File Coverage

blib/lib/Language/XS.pm
Criterion Covered Total %
statement 112 141 79.4
branch 19 52 36.5
condition 11 35 31.4
subroutine 20 26 76.9
pod 11 19 57.8
total 173 273 63.3


line stmt bran cond sub pod time code
1             package Language::XS;
2              
3 2     2   1163 use strict 'subs';
  2         4  
  2         49  
4 2     2   8 use Carp;
  2         3  
  2         139  
5 2     2   16 use Config;
  2         5  
  2         72  
6 2     2   9 use File::Spec;
  2         9  
  2         5134  
7             #use ExtUtils::MakeMaker; # argh, but knows useful info we can't deduce otherwise!
8              
9             require Exporter;
10              
11             #BEGIN { $^W=0 } # I'm fed up with bogus and unnecessary warnings nobody can turn off.
12              
13             @ISA = qw(Exporter);
14              
15             @EXPORT = ();
16             @EXPORT_OK = ();
17             %EXPORT_TAGS = ();
18             $VERSION = 0.02;
19              
20             =head1 NAME
21              
22             Language::XS - Write XS code on the fly and load it dynamically.
23              
24             =head1 SYNOPSIS
25              
26             use Language::XS;
27              
28             =head1 DESCRIPTION
29              
30             This module allows C & XS-code creation "on-the-fly", i.e. while your
31             script is running.
32              
33             Here is a very simple example:
34              
35             # create a Language::XS-object
36             my $xs = new Language::XS cachedir => undef;
37             # add plain C to the header
38             $xs->hdr("#include ");
39             # add a c function (not using xs syntax)
40             $xs->cfun('printf ("I was called with %d arguments\n", items);');
41              
42             # now compile and find the code-reference
43             my $coderef = $xs->find;
44             # Now call it
45             $coderef->(1, "Zwei", 1/3);
46              
47             =head1 METHODS
48              
49             =cut
50              
51             #my $xsloader = eval { require XSLoader };
52             #$xsloader or require DynaLoader;
53              
54             # *NIX-specifix
55             my $uid = "XUzNaIcQeUfExxIaD0000";
56 1     1 0 9 sub next_uid { ++$uid }
57              
58             # *NIX-specific
59             my $default_cache = "$ENV{HOME}/.perl-xs-cache";
60              
61             my %tmpdirs;
62              
63             # this is *NIX-specific, and rather clumsy
64             sub tmpdir_create {
65 1     1 0 3 my $self = shift;
66 1   50     7 my $prefix = ($ENV{TMPDIR}||"/tmp")."/language-xs-";
67 1         14 my $suffix = "T${$}000";
68 1         232 $suffix++ while !mkdir "$prefix$suffix", 0700;
69 1         5 $tmpdirs{"$prefix$suffix"} = 1;
70 1         6 "$prefix$suffix";
71             }
72              
73             sub tmpdir_cleanup {
74 1     1 0 2 my $dir = shift;
75 1 50       8 return unless exists $tmpdirs{$dir};
76 1 50       43 if (opendir DIR, $dir) {
77 1         38 while (my $name = readdir DIR) {
78 4         170 unlink "$dir/$name";
79             }
80 1         14 closedir DIR;
81             }
82 1         107 rmdir $dir;
83 1         10 delete $tmpdirs{$dir};
84             }
85              
86             END {
87 2     2   769 for $dir (keys %tmpdirs) {
88 1         4 tmpdir_cleanup $dir;
89             }
90             }
91              
92             sub sanitize_id($) {
93 4     4 0 7 my $id = shift;
94 4         10 $id =~ y{0-9a-zA-Z\-_.:/\\}{}cd;
95 4         9 $id;
96             }
97              
98             =head2 new attr => value, ...
99              
100             Creates a new Language::XS object. Known attributes are:
101              
102             id a unique id that woill be shared among all modules
103             cachedir the common directory where shared objects should be cached.
104             set to undef when you want to disable sharing (must be
105             an absolute path)
106              
107             Default values will be supplied when necessary. Two common idioms are:
108              
109             $xs = new Language::XS; # caching enabled
110             $xs = new Language::XS cachedir => undef; # caching disabled
111              
112             =cut
113              
114             # id = unique id (for caching)
115             sub new {
116 1     1 1 415 my $class = shift;
117 1         5 $self = bless {
118             id => next_uid(),
119             cachedir => $default_cache,
120             dirty => 0,
121             @_
122             };
123 1         11 $self->{id} = sanitize_id $self->{id};
124 1         5 $self->{package} = "language_xs_$self->{id}";
125 1 50 0     5 $self->{sofile} ||= File::Spec->catfile($self->{cachedir}, "$self->{id}.$Config{dlext}") if $self->{cachedir};
126 1         3 $self;
127             }
128              
129             sub DESTROY {
130 0     0   0 my $self = shift;
131 0         0 tmpdir_cleanup($self->{tmpdir});
132             }
133              
134             =head2 cached
135              
136             Returns true when as shared object with the given id already exists. This obviously only makes sense when
137             you gave the module a unique id.
138              
139             =cut
140              
141             sub cached($) {
142 0     0 1 0 my $self = shift;
143 0 0       0 $self->{cachedir} && -e $self->{sofile};
144             }
145              
146             # prepend linenumber-heuristic
147             sub _lineno($$) {
148 2     2   4 my ($code, $id) = @_;
149 2         16 my @c = caller(1);
150 2         4 my $line = $c[2]*1;
151 2 50       7 $id = $c[3] unless $id;
152 2         5 $id = sanitize_id $id;
153 2 50       10 $line++ if $code =~ /\n/; # assume here document
154 2         22 "\n#line $line \"$id\"\n".$code."\n";
155             }
156              
157             =head2 hdr sourcecode, [id]
158              
159             Add C to the header portion. Similar to the header portion of
160             an XS module, you can insert any valid C-code here. Most often you'd add
161             some include directives, though.
162              
163             C can be used to identify this portion (for error messages).
164              
165             =cut
166              
167             # add simple C code as header
168             sub hdr($$$) {
169 1     1 1 156 my ($self, $code, $id) = @_;
170 1         3 $self->{dirty} = 1;
171 1         5 $self->{hdr} .= _lineno($code, $id);
172             }
173              
174             =head2 cfun functionbody, [id], [prototype]
175              
176             Adds a XS function whose body is given in C. Unlike
177             XS, you have to do argument processing (i.e. fiddling with C)
178             yourself. C specifies the function name (for C or error
179             messages), and can be omitted (which results in a default name).
180              
181             C is an optional string that specifies the perl
182             protoype. Remember that only the parser will evaluate prototypes.
183              
184             =cut
185              
186             # add a function body written in C
187             sub cfun($$$$) {
188 1     1 1 5 my ($self, $body, $id, $prototype) = @_;
189 1         3 $self->{dirty} = 1;
190 1   50     3 $self->{fun}{$id||"default"} = [_lineno($body, $id), $prototype];
191             }
192              
193             =head2 xsfun xs-source
194              
195             Similar to C, but is able to parse normal XS syntax (most of it,
196             that is). Pity that I haven't yet implemented this function, since that
197             would require serious recoding of C.
198              
199             =cut
200              
201             # add a function body written in XS
202             sub xsfun($$$) {
203 0     0 1 0 croak "add_xsfun not yet, implemented, use add_cfun isntead";
204 0         0 my ($self, $body) = @_;
205             }
206              
207             =head2 uselib lib...
208              
209             Link against all the libraries given as arguments. The libraries should be
210             specified as strings of the form C<-llibrary>. Additional search paths can
211             be given using C<-L/path/to/libs>. See L.
212              
213             =cut
214              
215             sub uselib {
216 0     0 1 0 my $self = shift;
217 0         0 $self->{libs} .= " @_";
218             }
219              
220             =head2 incpath path...
221              
222             Add additional include paths. These paths are prepended to the other
223             include paths.
224              
225             =cut
226              
227             sub incpath {
228 0     0 1 0 my $self = shift;
229 0         0 for (@_) {
230 0         0 $self->{incpath} .= " -I$_";
231             }
232             }
233              
234             sub gen_cfile {
235 1     1 0 3 my $self = shift;
236 1         2 my $boot;
237 1 50       101 open CFILE, ">".$self->{cfile} or croak "$self->{cfile}: $!";
238 1         26 print CFILE "#include \"EXTERN.h\"\n",
239             "#include \"perl.h\"\n",
240             "#include \"XSUB.h\"\n",
241             $self->{hdr}."\n";
242 1         2 while (my ($id, $def) = each %{$self->{fun}}) {
  2         11  
243 1         4 my ($body, $prot) = @$def;
244 1         3 $id = sanitize_id $id;
245 1         4 my $fun = "$self->{package}_$id";
246 1         5 print CFILE "XS($fun)\n{\n dXSARGS;\n\n$body\n\n XSRETURN_EMPTY;\n}\n\n";
247 1 50       6 $proto = $proto ? "newXSproto(0, $fun, __FILE__, \"$prot\")"
248             : "newXS(0, $fun, __FILE__)";
249 1         7 $boot .= " hv_store (hv, \"$id\", ".(length $id).", newRV_noinc ((SV *)$proto), 0);\n";
250             }
251              
252 1         5 print CFILE "XS(boot_$self->{package})\n{\n dXSARGS;\n HV *hv = (HV *)SvRV (ST (0));\n$boot}\n\n";
253 1         167 close CFILE;
254             }
255              
256             # somewhat os-specific
257             sub run_cmd {
258 2     2 0 376 my ($wd, $cmd) = @_;
259 2 50       1888 if (0 == open CMD, "-|") {
260 0 0       0 open STDERR, ">&STDOUT" or exit 1;
261 0 0       0 chdir $wd or die "unable to cd to '$wd': $!\n";
262 0         0 exec $Config{sh}, "-c", $cmd;
263 0         0 exit 127; # unreachable
264             }
265             #open CMD, "$cmd 2>&1 |" or croak "unable to execute '$cmd': $!";
266 2         151 local $/;
267 2         382096 $cmd = ;
268 2         215 ((close CMD), $cmd);
269             }
270              
271             # very os-specific(!)
272             sub gen_sofile {
273 1     1 0 4 my $self = shift;
274 1         5 local $^W = 0; # perl is rather borken
275 1         1070 ($self->{ofile} = $self->{cfile}) =~ s/\.c$/$Config{_o}/;
276 1         4000 my ($ok, $msg) = run_cmd $self->{tmpdir},
277             "$Config{cc} -c $self->{incpath} $Config{ccflags} $Config{optimize} $Config{large} ".
278             "$Config{split} $Config{cccdlflags} $self->{cflags} -I$Config{archlibexp}/CORE $self->{cfile}";
279 1         65 $self->{messages} .= $msg;
280 1   33     97 $ok &&= -e $self->{ofile};
281 1 50       14 if ($ok) {
282             # perl_archive is os-specific(!) also export_list(!)
283 1 50       22 if ($self->{libs}) {
284 0         0 require ExtUtils::Liblist;
285 0         0 @$self{'extralibs', 'bsloadlibs', 'ldloadlibs', 'ld_run_path'} =
286             ExtUtils::Liblist::ext($self, $self->{libs}, 0);
287             }
288 1         45 ($ok, $msg) = run_cmd $self->{tmpdir},
289             "LD_RUN_PATH=\"$self->{ld_run_path}\" $Config{ld} -o $self->{sofile} ".
290             "$Config{lddlflags} $self->{ofile} $self->{otherldflags} $self->{perl_archive} ".
291             "$self->{ldloadlibs} $self->{export_list}";
292 1   33     92 $ok &&= -e $self->{sofile};
293 1         9 $self->{messages} .= $msg;
294 1 50       13 if ($ok) {
295 1         62 chmod 0755, $self->{sofile};
296             }
297             }
298 1         9 unlink $ofile;
299 1         6 $self->{dirty} = 0;
300 1         30 $ok;
301             }
302              
303             =head2 gen
304              
305             Create the shared object file. This method is called automatically by
306             C and even by C. This function returns a truth status and
307             fills the messages attribute (see C) with any compiler/linker
308             warnings or errors.
309              
310             =cut
311              
312             # generate code (& optionally cache)
313             sub gen($) {
314 1     1 1 7 my $self = shift;
315 1   33     8 $self->{tmpdir} ||= tmpdir_create();
316 1         5 $self->{messages} = "";
317 1         2 delete $self->{loaded};
318 1 50 33     52 $self->{sofile} ||= File::Spec->catfile($self->{tmpdir}, "$self->{id}.$Config{dlext}") unless $self->{sofile};
319 1   33     17 $self->{cfile} ||= File::Spec->catfile($self->{tmpdir}, "$self->{id}.c");
320 1 50 33     5 if ($self->{cachdir} && ! -d $self->{cachedir}) {
321 0 0       0 mkdir $self->{cachedir},0755 or croak "unable to create '$self->{cachedir}': $!";
322             }
323 1   33     26 my $ok = $self->gen_cfile && $self->gen_sofile;
324 1         115 unlink $self->{cfile};
325 1 50       11 tmpdir_cleanup($self->{tmpdir}) if $self->{cachedir};
326 1         17 $ok;
327             }
328              
329             =head2 messages
330              
331             Returns the compiler messages (created & updated by C).
332              
333             =cut
334              
335             sub messages {
336 1     1 1 390 shift->{messages};
337             }
338              
339             =head2 load
340              
341             Tries to load the shared object, generating it if necessary. Returns a
342             truth status.
343              
344             =cut
345              
346             sub load {
347 1     1 1 6 my $self = shift;
348 1 50       7 if (!$self->{loaded}) {
349 1 50 33     17 if (!$self->{sofile} || $self->{dirty}) {
350 0 0       0 $self->gen or return 0;
351             }
352 1         21 require DynaLoader;
353 1 50       171 $self->{dl_lib} = DynaLoader::dl_load_file($self->{sofile}) or croak "unable to load $self->{sofile}";
354 1 50       22 $self->{dl_boot} = DynaLoader::dl_find_symbol($self->{dl_lib}, "boot_$self->{package}") or croak "no entry point found";
355 1         44 $self->{dl_boot_cv} = DynaLoader::dl_install_xsub(__PACKAGE__."boot_$self->{package}", $self->{dl_boot});
356 1         9 $self->{dl_hash} = { };
357 1         37 $self->{dl_boot_cv}->($self->{dl_hash});
358 1         10 $self->{loaded} = 1;
359             }
360 1         5 $self->{loaded};
361             }
362              
363             =head2 find [id]
364              
365             Find the function (either xs or c) with id C and return a code-ref to
366             it. If C is omitted, the default function (see C) is returned
367             instead. If no shared object is loaded, calls C.
368              
369             =cut
370              
371             sub find {
372 1     1 1 172 my ($self, $fun) = @_;
373 1 50       5 $self->load unless $self->{loaded};
374 1   50     14 $self->{dl_hash}{$fun||"default"};
375             }
376              
377             =head1 BUGS/PROBLEMS
378              
379             Requires a C compiler (or even worse: the same C compiler perl was compiled with).
380              
381             Does (most probably) not work on many os's, especially non-unix ones.
382              
383             You cannot yet use normal XS syntax.
384              
385             Line number handling could be better.
386              
387             =head1 AUTHOR
388              
389             Marc Lehmann .
390              
391             =head1 SEE ALSO
392              
393             perl(1).
394              
395             =cut
396              
397             # compatibility cruft for ExtUtils::Liblist
398              
399             sub lsdir {
400 0     0 0   my($self) = shift;
401 0           my($dir, $regex) = @_;
402 0           my(@ls);
403 0           require DirHandle;
404 0           my $dh = new DirHandle;
405 0 0 0       $dh->open($dir || ".") or return ();
406 0           @ls = $dh->read;
407 0           $dh->close;
408 0 0         @ls = grep(/$regex/, @ls) if $regex;
409 0           @ls;
410             }
411              
412             1;
413