0

I need to package my python code that uses the saxonpy library into a single executable (for use in Windows platform via the command line). I tried several combinations but the latest attempt is:

(stackoverflow-C7krtO5X-py3.10) PS C:\pythonProject\stackoverflow> pyinstaller --onefile .\main3a.py --name "transfo.exe" --hidden-import nodekind --hidden-import saxonpy

I also tried adding

--add-binary "C:\pythonProject\saxonpy\saxonc_home\libsaxonhec.dll;."

When I run the exe that pyinstaller creates, I get:

PS C:\pythonProject\stackoverflow\dist> .\transfo.exe
Unable to load C:\Users\<username>\AppData\Local\Temp\_MEI257642\saxonpy/saxonc_home\libsaxonhec.dll
Error: : No error

It does work if I set the SAXONC_HOME environment variable to point to a saxonc folder. e.g.:

C:\Users\<username>\AppData\Local\pypoetry\Cache\virtualenvs\stackoverflow-C7krtO5X-py3.10\Lib\site-packages\saxonpy\saxonc_home

probably unnecessary extra info:

Contents of the poetry pyproject.toml file:

[tool.poetry]
name = "stackoverflow"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]

[tool.poetry.dependencies]
python = "^3.10"
saxonpy = "^0.0.3"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
import os
import xml.etree.ElementTree as ET
from saxonpy import PySaxonProcessor

def main():
    print('starting code...')
    source_XML = '''
        <cities>
            <country name="Denmark" capital="Copenhagen"/>
            <country name="Germany" capital="Berlin"/>
            <country name="France" capital="Paris"/>
        </cities>
    '''
    parentroot = ET.fromstring(source_XML)
    # has to be unicode for proc.parse_xml()
    xml_str = ET.tostring(parentroot, encoding='unicode', method='xml')

    try:
        with PySaxonProcessor(license=False) as proc:
            proc.set_cwd(os.getcwd())
            xsltproc = proc.new_xslt30_processor()
            xslt30_transformer = xsltproc.compile_stylesheet(stylesheet_file="transformer.xsl")

            xml_doc = proc.parse_xml(xml_text=xml_str)
            # set_initial_match_selection belongs to xslt30_transformer, not xsltproc or proc!
            xslt30_transformer.set_initial_match_selection(xdm_value=xml_doc)
            xslt30_transformer.apply_templates_returning_file(xdm_node=xml_doc,
                                                              output_file="output.xml")

            print("transform complete. file should be created.")
    except Exception as e:
        print(f"exception occured: {e}")

if __name__ == "__main__":
    main()

contents of the transformer.xsl file:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/">
        <cities>
            <xsl:for-each select="cities/country">
                <city name="{@capital}" isCapital="true"/>
            </xsl:for-each>
        </cities>
    </xsl:template>
</xsl:stylesheet>
woodduck
  • 339
  • 2
  • 12
  • Yes the SAXONC_HOME environment variable is required to locate the libsaxonhec.dll library – ond1 May 26 '22 at 13:10
  • Yes, but what do I point it to when I have only the standalone executable? I need to also copy the saxonpy folder to the target computer and then set the $env to point to it. – woodduck May 26 '22 at 19:03
  • 1
    I am not sure how it would work, but have you tried pointing it to the executable? We could also put forward a case for a change of design in how we look for the library – ond1 May 26 '22 at 22:33
  • I will try to construct the executable with your instructions above – ond1 May 27 '22 at 06:22
  • 1
    Yes I have but it didn't help. PS C:\pythonProject\stackoverflow\dist> $env:SAXONC_HOME = "." PS C:\pythonProject\stackoverflow\dist> .\transfo.exe Unable to load C:\Users\username\AppData\Local\Temp\_MEI257642\saxonpy/saxonc_home\libsaxonhec.dll – woodduck May 27 '22 at 07:22
  • It should be possible to modify the C code on latest version of SaxonC to look in a relative location for the library. But I would have to test this – ond1 May 27 '22 at 08:54
  • Hi I built the executable, but when I run it I get the following error: FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\User\\AppData\\Local\\Temp\\_MEI72362\\.load-order-saxonpy-0.0.3' Any ideas? – ond1 May 27 '22 at 13:52
  • 1
    I had to update python to version 3.10. I have now reproduced the same error with libsaxonhec.dll – ond1 May 27 '22 at 16:12
  • It should be possible to set the environment variable from within the python script. I still trying to get it to work myself, but maybe this can help: https://stackoverflow.com/questions/8365394/set-environment-variable-in-python-script – ond1 May 27 '22 at 16:42

1 Answers1

1

I had a similar issue - my first command line was:

pyinstaller --hidden-import nodekind --onefile --add-binary="libsaxonhec.dll;." MyScript.py

Then I fixed the directory path:

pyinstaller --hidden-import nodekind --onefile --add-binary="libsaxonhec.dll;saxonpy\saxonc_home" MyScript.py

The point is that the DLL needs to be put into the expected directory, not just the current directory "."

However, when I used this command line it revealed a second issue, that the Java VM could not start:

Excelsior JRE directory "C:\Users\me\Local\Temp\_MEI138362\saxonpy\saxonc_home\rt" not found.
JNI_CreateJavaVM() failed with result: -1

So, I modified the command line again to include all the binaries from saxonpy:

pyinstaller --hidden-import nodekind --onefile --add-binary="libsaxonhec.dll;saxonpy\saxonc_home" --collect-binaries saxonpy MyScript.py

Now the Java VM starts OK, but it can't find the timezone mappings:

can't open C:\Users\me\AppData\Local\Temp\_MEI208802\saxonpy\saxonc_home\rt\lib\tzmappings.
Error: No styleheet found. Please compile stylsheet before calling transformToString or check exceptions

This particular error may be happening because I am using XSLT, so maybe you won't see this. But anyway, each time I'm getting a bit further :-)

The final fix was to collect "all", not just the "binaries" for saxonpy:

pyinstaller --hidden-import nodekind --onefile --add-binary="libsaxonhec.dll;saxonpy\saxonc_home" --collect-all saxonpy MyScript.py

Conclusion

You have to include a whole bunch of stuff from saxonpy (which makes the EXE quite large - circa 41MB for my simple script) and that has to be directed into the expected path, but it does then work.

Dave C
  • 186
  • 1
  • 3
  • Thanks for that. I'll have to try it when I next need to do this. – woodduck Aug 04 '23 at 08:00
  • have you tried using the official SaxonC saxonche from: https://pypi.org/user/saxonica/ which no longer packages nodekind separately. – ond1 Aug 05 '23 at 19:40