# Building and testing XS

## The cycle

```bash
perl Makefile.PL      # writes the Makefile, resolves the library flags
make                  # xsubpp .xs → .c, compile, link into blib/arch
make test             # runs t/ against blib
```

`make` regenerates the `.c` whenever the `.xs` is newer, so an edit to XS needs a
`make`, not just `prove`. Running `prove -lr t/` without a preceding `make` tests
whatever `blib/` last held — a stale `.so` passing its own old tests is a plausible
and very confusing green run.

## Makefile.PL

```perl
WriteMakefile(
  NAME   => 'Foo',
  LIBS   => [ '-lfoo' ],              # linker flags
  INC    => '-I/usr/include/foolib',  # compiler include flags
  OBJECT => 'Foo$(OBJ_EXT)',          # only when the basename differs or several
  DEFINE => '-DSOMETHING',
);
```

`OBJECT` defaults to `$(BASEEXT)$(OBJ_EXT)` — the distribution's last name component.
Set it when the `.xs` basename differs from that, or when several object files link
into one extension.

Hardcoded `LIBS`/`INC` are fine for a system library that is genuinely everywhere.
Anything else resolves them at configure time — through an `Alien::*` module
(`perl-alien`) or `ExtUtils::PkgConfig`.

## ppport.h

`ppport.h` is generated by `Devel::PPPort` and backfills newer Perl API onto older
Perls. Regenerate it rather than editing it:

```bash
perl -MDevel::PPPort -e 'Devel::PPPort::WriteFile()'  # write/refresh ppport.h
perl ppport.h                                       # audit the .xs files against it
```

The audit run reads the XS sources and reports which API needs a `NEED_` define,
which constructs limit the minimum supported Perl, and what can be simplified.

Each `NEED_foo` must appear **before** `#include "ppport.h"` and exactly once per
compilation unit — it is what makes ppport.h emit a static implementation of that
function:

```c
#define NEED_mg_findext     /* mg_findext is 5.14+; this covers older Perls */
#include "ppport.h"
```

## Toolchain versions

xsubpp is `ExtUtils::ParseXS`, versioned independently of Perl and upgradable from
CPAN — so an XS feature gated on an xsubpp version is a *toolchain* requirement, not
a Perl one:

```bash
perl -MExtUtils::ParseXS -e 'print "$ExtUtils::ParseXS::VERSION\n"'
```

A typemap that needs a minimum states `REQUIRE: 3.60` on its first line. Without
that line, a construct like `${type}` expanding `::` to `__` silently produces
uncompilable C on older toolchains.

## Reading the generated C

When a compile error points into generated code, generate it directly and read it:

```bash
perl -MExtUtils::ParseXS -e \
  'ExtUtils::ParseXS->new->process_file(filename=>"Foo.xs", output=>"/tmp/gen.c",
                                        typemap=>"typemap")'
```

The `#line` directives map every block back to its source line in the `.xs`, and the
argument-conversion code the typemap produced is right there to compare against what
it was supposed to produce. Keep the file out of the distribution — the `.c` beside
the `.xs` is a build artifact and belongs in `.gitignore`.

## Testing what only XS gets wrong

Perl-level tests cover the API. Three failure modes need their own shape:

**Crashes.** A refcount or lifetime bug segfaults, and a segfault takes `prove` down
instead of failing a test. Run each such scenario in a forked child and assert on
what the child reports:

```perl
use POSIX ();

sub run_in_child {
    my ($name, $code, $expect) = @_;
    pipe(my $rd, my $wr) or die "pipe: $!";
    my $pid = fork() // die "fork: $!";
    if (!$pid) { print {$wr} eval { $code->() } // "ERROR|$@"; close $wr; POSIX::_exit(0) }
    close $wr;
    chomp(my $got = <$rd> // '');
    waitpid $pid, 0;
    is $got, $expect, $name;   # a child killed by SIGSEGV reports nothing at all
}
```

Cover every way of losing a variable, not just the one that reads naturally:
out of scope, `undef $x`, and `$x = something_else` fail differently.

**Leaks.** `Test::LeakTrace`'s `no_leaks_ok { … }` runs the block twice and compares
the SV count. It sees Perl-level leaks; C-level ones (a `malloc` never freed behind
a still-collected SV) need valgrind:

```bash
PERL_DESTRUCT_LEVEL=2 valgrind --leak-check=full perl -Mblib t/03-whatever.t
```

`PERL_DESTRUCT_LEVEL=2` makes Perl free its own arenas at exit, so what valgrind
still reports is yours.

**Blocking.** A C call that hangs turns a failing test into a hanging suite. Guard
any call that can block on the network with `alarm`, so the failure mode is a
reported failure rather than a CI job that runs until the timeout.

For debugging symbols, rebuild with `perl Makefile.PL OPTIMIZE='-g -O0'`.
