http://www.perlmonks.org?node_id=11104628

syphilis has asked for the wisdom of the Perl Monks concerning the following question:

Hi,
In an XS file I have the following line of code:
printf("%.16e", sqrt(2.0));
I want to rewrite that line as:
printf(MY_FORMAT, sqrt(2.0));
I also wish to then define MY_FORMAT to "%.16e" in the Makefile.PL.

This is normally achieved by assigning an appropriate value to DEFINE in the WRITEMAKEFILE() section of the Makefile.PL - eg:
WRITEMAKEFILE( .... DEFINE => '-DMY_FORMAT="%.16e"', .... );
But that specific incantation fails to work - and I haven't been able to hit on the incantation that *does* work.
Update: By "not work", I mean that it either won't compile or it leads to runtime crashes (depending upon the actual incantation I've tried).
Any help would be much appreciated.

I've also spent a couple of hours trying to get this working in Inline::C. If I can get it to work in Inline::C, then I'll be able to use the incantation in the Makefile.PL that Inline::C generates.
Here's that Inline::C script:
use strict; use warnings; use Config; use Inline C => Config => USING => 'ParseRegExp', CCFLAGSEX => '-DMY_FORMAT="%.16e"', BUILD_NOISY => 1, ; use Inline C =><<'EOC'; SV * foo(void) { printf(MY_FORMAT, sqrt(2.0)); printf("\n"); } EOC foo();
I've tried various escapes around "%.16e" but nothing has been successful.
In C, it's pretty simple to achieve:
C:\_32\C>type try.c #include <stdio.h> #include <math.h> int main(void) { printf(MY_FORMAT, sqrt(2.0)); return 0; } C:\_32\C>gcc -o try.exe try.c -DMY_FORMAT=\"%.16e\" C:\_32\C>try 1.4142135623730951e+000 C:\_32\C>
I'm not all that bothered if it turns out that Inline::C can't handle the construct, but I *would* like to know how to do it in the XS file via the Makefile.PL if, indeed, that's possible at all.

Cheers,
Rob

Replies are listed 'Best First'.
Re: Defining an XS symbol in the Makefile.PL
by jcb (Parson) on Aug 18, 2019 at 05:20 UTC

    Normally, I would suggest adding backslashes, but I do not know exactly how many layers of quoting this would need.

    Perl has a neat feature: the custom quote operators. I suggest:

    WriteMakefile( .... DEFINE => q['-DMY_FORMAT="%.16e"'], .... );

    ... which should put the entire parameter in "shell" single quotes.

    Another option is to follow the method used by GNU autoconf and write out a "config.h" file with '#define MY_FORMAT "%.16e"' in it somewhere.

      ... which should put the entire parameter in "shell" single quotes

      Ah, "the shell", my pet problem.

      You are assuming that putting something in single quotes will prevent any shell from interpreting it. Sorry, but that assumption is wrong. Both the old (command.com) and the standard shell (cmd.exe) on Windows don't handle single quotes that way.

      Generating a header file to be included by the C/XS code seems to be the more portable approach.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
        Generating a header file to be included by the C/XS code seems to be the more portable approach

        That's probably sort of what I'm doing - and my initial post oversimplified things.
        See this Scalar-List-Utils bug report for a better picture.

        The real issue is that MY_FORMAT needs to be defined to "%.17" (if NV is 64 bit), to be defined to "%.21" (if NV is 80 bit), to be defined to "%.36" (if NV is 128 bit).
        It's quite easy during the Makefile.PL processing to determine which it needs to be - and surely the simplest thing to do is to just have the Makefile.PL define MY_FORMAT to the correct string.
        But you're right ... and jcb's suggestion (along with several variations thereof) is not working.

        At the moment, the Makefile.PL is doing:
        .... if($Config{nvsize} == 8) { $nvtype = 'LU_NV_IS_64_BIT' } # double o +r 8-byte long double elsif(length(sqrt 2) > 25) { $nvtype = 'LU_NV_IS_128_BIT' } # IEEE lon +g double or __float128 else { $nvtype = 'LU_NV_IS_80_BIT' } # extended + precision long double $defines .= " -D$nvtype"; .... WriteMakefile ( .... DEFINE => $defines, .... );
        And the XS file is rewritten to redefine the symbol to the appropriate value:
        #if defined(LU_NV_IS_64_BIT) #define MY_FORMAT "%.17" #elif defined(LU_NV_IS_80_BIT) #define MY_FORMAT "%.21" #else #define MY_FORMAT "%.36" #endif
        It's working fine .... but I feel that I should be able to define MY_FORMAT directly within the Makefile.PL and avoid having to add that preprocessing to the XS file ??

        Cheers,
        Rob

        It has been a while since I have developed on Windows, but if I recall correctly, the special variant if $^O eq 'MSWin32' is:

        WriteMakefile( .... DEFINE => q["-DMY_FORMAT=""%.16e"""], .... );

        Since Makefile.PL is actually a Perl program, these can be combined as:

        WriteMakefile( .... DEFINE => $^O eq 'MSWin32' ? q["-DMY_FORMAT=""%.16e"""] : q['-DMY_FO +RMAT="%.16e"'], .... );
Re: Defining an XS symbol in the Makefile.PL (largely solved)
by syphilis (Archbishop) on Aug 21, 2019 at 04:32 UTC
    Hi,
    As regards my problems in getting the CCFLAGSEX setting right in this script (from my initial post):
    use strict; use warnings; use Config; use Inline C => Config => USING => 'ParseRegExp', CCFLAGSEX => '-DMY_FORMAT=\\"%.16e\\"', BUILD_NOISY => 1, ; use Inline C =><<'EOC'; SV * foo(void) { printf(MY_FORMAT, sqrt(2.0)); printf("\n"); } EOC foo();
    I've found that it has nothing to do with the versions of Inline::C or ExtUtils::MakeMaker or perl itself.
    It's the flavour of make that's critical.

    If $Config{make} is gmake then the "%" needs to be replaced with "%%". But if $Config{make} is dmake then it has to be the single "%".
    Essentially, whatever works with dmake will also work with gmake so long as that replacement is made.
    For example, either of these will work with dmake (but not with gmake):
    CCFLAGSEX => '-DMY_FORMAT=\\"%.16e\\"', CCFLAGSEX => "-DMY_FORMAT=\\\"%.16e\\\"", CCFLAGSEX => q["-DMY_FORMAT=\\"%.16e\\""],
    and either of these will work with gmake (but not with dmake):
    CCFLAGSEX => '-DMY_FORMAT=\\"%%.16e\\"', CCFLAGSEX => "-DMY_FORMAT=\\\"%%.16e\\\"", CCFLAGSEX => q["-DMY_FORMAT=\\"%%.16e\\""],
    The situation with gmake looks very much like a bug to me.
    If it is a bug, I'm guessing it's in perl (probably EU::MM), though it could also be a bug in gmake itself.
    That's all I know at the moment.

    Cheers,
    Rob

      Oh, that is another issue: % is also special in make's language — it is used in forming patterns, where GNU make has excellent support and I do not know about dmake. I would strongly recommend passing a number (as I offered in Re^4: Defining an XS symbol in the Makefile.PL and Re^8: Defining an XS symbol in the Makefile.PL; you can combine the techniques and use the "odd" probe code with the "later" XS code to build the format from LU_NV_PREC) instead of trying to get a complete format string from Makefile.PL to XS code through a command-line option.

        ... % is also special in make's language — it is used in forming patterns ...

        Yes, I know that, and it may well be part of the problem.
        However, when I run the script on my Ubuntu box (where GNU make is also used) I find that it needs only the single "%", same as dmake.
        Can we therefore assume that the bug is in the Win32 version of "make" that I'm using ?

        On windows, I've been using:
        GNU Make 3.82.90 Built for i686-pc-mingw32 Copyright (C) 1988-2012 Free Software Foundation, Inc.
        and it makes no difference when I switch to:
        GNU Make 4.2.1 Built for x86_64-pc-msys Copyright (C) 1988-2016 Free Software Foundation, Inc.
        So, if the problem lies with GNU make on Windows, it seems it's a thoroughly embedded problem.

        I know that "%%" is the way to escape the "%" in (s)printf's formatting pattern.
        According to https://www.cmcrossroads.com/article/gnu-make-escaping-walk-wild-side, in GNU make one escapes the "%" with a backslash - which was another approach I had tried, and found to be unsuccessful.
        I'll investigate the possibility that somewhere in EU::MM there's a (s)printf call that's made when make=gmake, but not when make=dmake. (However, I think it unlikely that would happen.)

        The problem of how to workaround the issue is, in my view, solved.
        I'd just like to understand why and how that issue exists.

        Cheers,
        Rob
Re: Defining an XS symbol in the Makefile.PL
by bliako (Monsignor) on Aug 19, 2019 at 22:20 UTC

    After reading the answers you got, I am for keeping the MY_FORMAT as abstract/high-level as possible. That is -DMY_FORMAT=LU_NV_IS_64_BIT is prefered to -DMY_FORMAT='%.17'.

Re: Defining an XS symbol in the Makefile.PL (quoting)
by Anonymous Monk on Aug 19, 2019 at 01:33 UTC

    Hi

    $ ack MY_FORMAT * Goner2.c 170: printf(MY_FORMAT, sqrt(2.0)); Goner2.xs 14: printf(MY_FORMAT, sqrt(2.0)); Makefile 18:# DEFINE => q["-DMY_FORMAT=\"%.16e\""] 165:DEFINE = "-DMY_FORMAT=\"%.16e\"" 315: PASTHRU_DEFINE="\"-DMY_FORMAT=\\\"%.16e\\\"\" $(PASTHRU_DEFINE +)"\ Makefile.PL 26: DEFINE => q{"-DMY_FORMAT=\"%.16e\""}, $ perl -Mblib -MGoner2 -e" Goner2::beGone() " 1.4142135623730951e+000

    ExtUtils::MM_Any

    $ perl -MExtUtils::MakeMaker - print MM->new->quote_literal('-DMY_FORMAT="%.16e"') __END__ Warning: Guessing NAME [Goner2] from current directory name. -DMY_FORMAT=\"%.16e\"

    rt://ExtUtils-MakeMaker?

    😳

      I don't quite follow, but I did try your suggestion of DEFINE => q{"-DMY_FORMAT=\"%.17\""} in the Makefile.PL.
      Note that I've altered the .16e to .17, as that's what's needed by the module I'm actually testing.
      That does allow the compilation to succeed and I do see this output during the build:
      gcc -c -s -O2 -DWIN32 -DWIN64 -DCONSERVATIVE -DPERL_TEXTMODE_SCRIPTS + -DPERL_IMPLICIT_CONTEXT -DPERL_IMPLICIT_SYS -DUSE_PERLIO -D__USE_MIN +GW_ANSI_STDIO -fwrapv -fno-strict-aliasing -mms-bitfields -s -O2 -D +VERSION=\"1.52\" -DXS_VERSION=\"1.52\" "-IC:\_64\perl530_810\lib\COR +E" -DPERL_EXT -DUSE_PPPORT_H "-DMY_FORMAT=\"%.17\"" ListUtil.c
      which, I think, looks right.
      However, the XS code that relies on MY_FORMAT being set correctly still fails.
      I therefore stuck the following in the XS code to see exactly what MY_FORMAT looks like:
      printf("\n**%s**\n", MY_FORMAT);
      And that prints out **.17**, showing quite clearly that the quotes && the percentage sign have been lost.

      This Inline::C script also fails to build:
      use strict; use warnings; use Config; use Inline C => Config => USING => 'ParseRegExp', CCFLAGSEX => q["-DMY_FORMAT=\"%.16e\""], BUILD_NOISY => 1, ; use Inline C =><<'EOC'; SV * foo(void) { printf(MY_FORMAT, sqrt(2.0)); printf("\n"); } EOC foo();
      During the build I get:
      gcc -c -iquote"C:/_32/C" -s -O2 -DWIN32 -DWIN64 -DCONSERVATIVE -DPER +L_TEXTMODE_SCRIPTS -DPERL_IMPLICIT_CONTEXT -DPERL_IMPLICIT_SYS -DUSE_ +PERLIO -D__USE_MINGW_ANSI_STDIO -fwrapv -fno-strict-aliasing -mms-bit +fields "-DMY_FORMAT=\"%.16e\"" -s -O2 -DVERSION=\"0.00\" -DXS_VERSI +ON=\"0.00\" "-IC:\_64\perl530_810\lib\CORE" try_pl_d937.c <command-line>: warning: missing terminating " character try_pl_d937.xs:1:10: fatal error: EXTERN.h: No such file or directory #include "EXTERN.h" ^~~~~~~~~~ compilation terminated.

      I am still quite open to testing other suggestions ... though I'm well and truly over trying to work it out for myself.

      Cheers,
      Rob
        Um...so what did mycode show?