Dual-Life / Devel-PPPort

Perl/Pollution/Portability
9 stars 28 forks source link

Devel-PPPort

NAME

Devel::PPPort internals for hackers

SYNOPSIS

So you probably want to hack Devel::PPPort?

Well, here's some information to get you started with what's lying around in this distribution.

DESCRIPTION

How to backport something

First, make sure that what you want to backport is documented. If it's worth backporting, it surely is worth documenting. Submit a documentation patch to https://github.com/Perl/perl5/issues if necessary. Also, Devel::PPPort cannot automatically generate proper information about the item without at least knowing its API prototype. It can get this from embed.fnc if the item is a function, but if it is a macro, there needs to be at least a =for apidoc line for Devel::PPPort to be able to figure things out on its own.

Next, figure out where to place your implementation. Look at all the files in parts/inc/ for one that fits what you're planning. If there isn't one, just start a new one and remember to include it from within PPPort_pm.PL. If you do create a new file, it's usually the best approach to just copy an existing file and use it as a template.

Each file holds all relevant data for implementing a certain part of the API:

In all sections, lines that begin with ## are completely ignored.

Implementation Section Details

You can implement API elements via C functions or macros, or simple variables. It is preferable to use a macro if feasible. Otherwise, the user must explicitly request that it get loaded, by defining a NEED__function_ (or variable) as described in ppport.h. If a function, foo is required, place its body in this =implementation section, like so:

#if { NEED foo }

char *
foo(pTHX_ const U8 *arg1, const U32 arg2, ...)
{
   ...
}

#endif

Similarly for a variable.

It's obviously best to use a macro if at all feasible. Sometimes what once was implemented with a macro now requires a function; perhaps an edge case was overlooked. Doing so will cause the new ppport.h to not be drop-in compatible with the older version, and can hence cause breakage. This incompatiblity (while easily solved) really needs to be stressed in documentation.

Testing

After you have furnished your implementation, you need to test it.

Special Makefile targets

You can use

make regen

to regenerate all of the autogenerated files. To get rid of all generated files (except for parts/todo/* and parts/base/*), use

make purge_all

That's it.

To automatically test Devel::PPPort with lots of different Perl versions, you can use the soak script. Just pass it a list of all Perl binaries you want to test.

Regenerating ppport.h and PPPort.pm

Devel::PPPort keeps two directories of generated files, in parts/base and parts/todo. The files in each are named after Perl version numbers. When a function or macro came into existence is indicated by placing its name in the corresponding file in parts/base. The files in parts/todo are the same, except they indicate the earliest release that ppport.h supports the element. The delta is effectively what ppport.h buys you.

The generation process described in this section creates these files. It does so by examining as many perl versions as are available to it. It tries to make sure each element actually compiles, and it runs the test scripts you have furnished on every version.

Ideally, this should be done before every release that includes new backporting and/or when blead has added new public API. At a minimum, it should be done as the next major Perl release comes out.

The process isn't platform independent. It has currently been tested only under Linux, and it definitely requires at least gcc and the nm utility. The process used to be problematic, with random failures. But it has now been fixed to be reliable.

Before starting the regeneration, you need to have gathered certain data. (Options listed below apply to the tools that eventually will use the data, and which are described further below).

Having done this, run devel/regenerate which wraps the following steps (which you could instead do by hand, but it's easy to forget things):

How to build gobs of versions of Perl

Devel::PPPort supports Perl versions between 5.003 and bleadperl. To guarantee this support, its good to have as many versions as possible to test on. dromedary currently has many such versions.

There is a tool to build all the different versions and configurations. You can find it in devel/buildperl.pl. It can currently build the following Perl releases:

5.003
5.004 - 5.004_05
5.005 - 5.005_04
5.6.x
5.7.x
5.8.x
5.9.x
5.1x.x
5.2x.x
5.3x.x

Implementation

Knowing which parts of the API are not backwards compatible and probably need Devel::PPPort support is another problem that's not easy to deal with manually. If you run

perl Makefile.PL --with-apicheck

a C file is generated by parts/apicheck.pl that is compiled and linked with Devel::PPPort. This C file has the purpose of using each of the public API functions/macros once.

The required information is derived from parts/embed.fnc (just a copy of bleadperl's embed.fnc), parts/apidoc.fnc (which is generated by devel/mkapidoc.pl and simply collects the rest of the apidoc entries spread over the Perl source code) and parts/ppport.fnc (which lists the API provided purely by Devel::PPPort, along with other elements that are tested only using ppport.h).

The generated C file (usually, apicheck.c) won't compile as-is with older perls. And even if it compiles, there's still a good chance of the dynamic linker failing at make test time. But that's on purpose!

We can use these failures to find changes in the API automatically. The Perl script devel/mktodo calls another script devel/mktodo.pl repeatedly to run Devel::PPPort on version after version of perl, in decreasing version order, so we start with blead and work backwards. The latter script generates an apicheck.c. It starts with the code that successfully worked in the previously tested Perl version, which should be the version one higher than the current one. Call the current one n, and the previous one n+1. The items that fail to compile in n, but did compile in n+1 must have become available in n+1. We run the Linux command nm to find those undefined symbols in n. We change apicheck.c to ignore (through #ifdef's) those and recompile, repeating until apicheck.c successfully compiles, the dynamic linker is happy, and make test runs on this version. Then we repeat the process for n-1, and so on. (Actually, this process may generate false positives, so by default each failing API call is checked again. If possible, this is done by generating an apicheck.c for just the one failing API.) Note that the make test is run using ppport.h during both passes.

Running devel/mktodo currently takes a couple hours on dromedary.

If you run it with the --nocheck option, it won't recheck the API calls that failed in the compilation stage and it'll take significantly less time. No one currently associated with maintaining this module understands under what circumstances it is safe to run with --nocheck.

By repeating the process over and over, we build up information on when every element first became supported. This information is stored in files in the parts/base directory, one file per version. The file for version n+1 is generated by running version n of perl.

We actually want a second piece of information, which is how much ppport.h buys you. What happens when regenerating is actually two entire runs through all the perls. The first is accomplished by calling devel/mktodo with the --base option. It automically will call devel/mktodo.pl with each version of perl, NOT using anything in ppport.h. When done the results indicate when each API element became available in stock perl, without using ppport.h.

And then the whole process is repeated, but this time ppport.h is included. The files are placed in parts/todo. Thus, at the end, we know when each element became available in modified perl, using ppport.h.

However, only the public API that is implemented as functions (and must appear in embed.fnc) plus macros whose calling sequence is documented can be checked this way. The final step in the process is calling devel/scanprov. It looks through the header files for when all the symbols provided by Devel::PPPort first became defined. It doesn't test the symbols or try to compile them, as it doesn't generally know the API, but it can tell that something exists in release n+1 but not n (by scanning the include files in the CORE directory of various Perl versions). (It does know if a macro has zero arguments or non-zero arguments, so it does get extra information from the zero argument ones.)

Files

Residing in parts/inc/ is the "heart" of Devel::PPPort. Each of the files implements a part of the supported API, along with hints, dependency information, XS code and tests. The files are in a POD-like format that is parsed using the functions in parts/ppptools.pl.

The scripts PPPort_pm.PL, RealPPPort_xs.PL and mktests.PL all use the information in parts/inc/ to generate the main module PPPort.pm, the XS code in RealPPPort.xs and various test files in t/.

You can get extra information from PPPort_pm.PL by setting the environment variable DPPP_CHECK_LEVEL to 1 or 2.

All of these files could be generated on the fly while building Devel::PPPort, but not having the tests in t/ will confuse TEST/harness in the core. Not having PPPort.pm will be bad for viewing the docs on search.cpan.org. So unfortunately, it's unavoidable to put some redundancy into the package.

Submitting Patches

If you've added some functionality to Devel::PPPort, please consider submitting a patch with your work to P5P by sending a pull request to

https://github.com/Dual-Life/Devel-PPPort/pulls.

When submitting patches, please only add the relevant changes and don't include the differences of the generated files. You can use the purge_all target to delete all autogenerated files.

Integrating into the Perl core

When integrating this module into the Perl core, be sure to remove the following files from the distribution. They are either not needed or generated on the fly when building this module in the core:

MANIFEST
META.yml
PPPort.pm

BUGS

No known bugs.

COPYRIGHT

Version 3.x, Copyright (C) 2004-2020, Marcus Holland-Moritz and Perl 5 porters

Version 2.x, Copyright (C) 2001, Paul Marquess.

Version 1.x, Copyright (C) 1999, Kenneth Albanowski.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

SEE ALSO

See ppport.h and devel/regenerate.