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 <[email protected]>"]
[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>
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
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.