{"id":7312,"date":"2019-01-31T19:00:12","date_gmt":"2019-01-31T18:00:12","guid":{"rendered":"https:\/\/pi3g.com\/?p=7312"},"modified":"2019-02-02T20:46:57","modified_gmt":"2019-02-02T19:46:57","slug":"using-nuitka-compiler-for-python3-on-alpine-linux-armhf-musl","status":"publish","type":"post","link":"https:\/\/pi3g.com\/de\/using-nuitka-compiler-for-python3-on-alpine-linux-armhf-musl\/","title":{"rendered":"Verwendung des nuitka-Compilers f\u00fcr python3 auf Alpine Linux ARMHF (musl)"},"content":{"rendered":"<h1>Motivation<\/h1>\n<p><a href=\"https:\/\/www.picockpit.com\/pidoctor\" target=\"_blank\">pidoctor<\/a> is written in Python (as there was no easy way to get Crystal to work on ARMHF \/ musl). <\/p>\n<p>This means a dependency on Python \u2013 which adds overhead. <\/p>\n<p>I suspect that this overhead is the reason that pidoctor will not run on 256 MB Raspberry Pi\u2019s \u2013 because the RAM is exhausted by all the packages which are required.<\/p>\n<p>Furthermore, it is desirable to have the distribution be as small a download as possible! This means Python has to be removed from the equation somehow, the package should produce a binary (as would be the way with Crystal).<\/p>\n<p>Luckily, there is nuitka.<\/p>\n<p>And \u2013 lucky you \u2013 I have worked out some kinks of using nuitka on Alpine Linux, ARMHF (maybe it will work for x86 too).<\/p>\n<h1>Nuitka<\/h1>\n<p><a href=\"http:\/\/nuitka.net\/\" target=\"_blank\">nuitka<\/a> is a fantastic piece of software, which will take a Python script and compile it to an executable, thereby speeding it up dramatically. <\/p>\n<p>nuitka does all the heavy lifting, you don\u2019t even need to adjust anything in your source code (methinks). You can run it like this:<\/p>\n<blockquote>\n<p>python3 -m nuitka &#8211;follow-imports &#8211;standalone pidoctor.py<\/p>\n<\/blockquote>\n<p>Note the &#8211;standalone flag, which will compile the necessary shared libraries into your &lt;projectname&gt;.dist folder \u2013 thereby the installation will be able to run without <strong>any dependency <\/strong>on Python!<\/p>\n<p>Please note: WordPress unfortunately fucks up the double dashes and makes them into single dashes. the \u2013m has a single dash, the \u2013follow-imports and the \u2013standalone have double dashes. <\/p>\n<h2>Installation of Nuitka on Alpine Linux (specifically arhmf, might work on other targets)<\/h2>\n<blockquote>\n<p>apk add python3-dev<\/p>\n<p>apk add chrpath<\/p>\n<p>pip3 install \u2013U nuitka<\/p>\n<\/blockquote>\n<p>optionally, if you are running in diskless mode (to add nuitka to the files which should be persisted):<\/p>\n<blockquote>\n<p>lbu add \/usr\/bin\/nuitka3-run<\/p>\n<p>lbu add \/usr\/bin\/nuitka3<\/p>\n<p>lbu add \/usr\/lib\/python3.6\/site-packages\/nuitka<\/p>\n<p>lbu commit \u2013d<\/p>\n<\/blockquote>\n<p>Note: nuitka\u2019s source is in Python. <\/p>\n<p>Check that nuitka runs:<\/p>\n<blockquote>\n<p>python3 \u2013m nuitka<\/p>\n<\/blockquote>\n<p>should execute <strong>without <\/strong>showing any syntax errors.<\/p>\n<h2>Fixing nuitka to run with Alpine Linux armhf<\/h2>\n<p>Important: WordPress unfortunately badly mangles the syntax. <a href=\"https:\/\/github.com\/Nuitka\/Nuitka\/issues\/237\" target=\"_blank\">Please refer to the GitHUB issue I opened<\/a>, where I also attached the patch files for a reference.<\/p>\n<p>nuitka calls several support binary utilities on your system, and expects them to return the results in a certain fashion. This is, where nuitka initially stumbles.<\/p>\n<p><strong>First error<\/strong><\/p>\n<blockquote>\n<p>pidoctor:\/opt\/pidoctor# python3 -m nuitka &#8211;follow-imports &#8211;standalone pidoctor.py<\/p>\n<p>Traceback (most recent call last):<\/p>\n<p>File &#8220;\/usr\/lib\/python3.6\/site-packages\/nuitka\/__main__.py&#8221;, line 188, in &lt;module&gt;<\/p>\n<p>main()<\/p>\n<p>File &#8220;\/usr\/lib\/python3.6\/site-packages\/nuitka\/__main__.py&#8221;, line 182, in main<\/p>\n<p>MainControl.main()<\/p>\n<p>File &#8220;\/usr\/lib\/python3.6\/site-packages\/nuitka\/MainControl.py&#8221;, line 846, in main<\/p>\n<p>Plugins.considerExtraDlls(dist_dir, module)<\/p>\n<p>File &#8220;\/usr\/lib\/python3.6\/site-packages\/nuitka\/plugins\/Plugins.py&#8221;, line 102, in considerExtraDlls<\/p>\n<p>for extra_dll in plugin.considerExtraDlls(dist_dir, module):<\/p>\n<p>File &#8220;\/usr\/lib\/python3.6\/site-packages\/nuitka\/plugins\/standard\/ImplicitImports.py&#8221;, line 372, in considerExtraDlls<\/p>\n<p><b>uuid_dll_path = locateDLL(&#8220;uuid&#8221;)<\/b><\/p>\n<p>File &#8220;\/usr\/lib\/python3.6\/site-packages\/nuitka\/utils\/SharedLibraries.py&#8221;, line 71, in locateDLL<\/p>\n<p>return dll_map[dll_name]<\/p>\n<p><b>KeyError: &#8216;libuuid.so.1.3.0&#8217;<\/b><\/p>\n<\/blockquote>\n<p>Reason behind the error: nuitka calls ldconfig \u2013p and expects it to list a list of cached shared libraries, in a format like this:<\/p>\n<p>libICE.so.6 (libc6,x86-64) =&gt; \/usr\/lib\/x86_64-linux-gnu\/libICE.so.6<\/p>\n<p>on Alpine Linux, ldconfig \u2013p fails \u2013 as there is no such option. ldconfig on Alpine Linux is incapable of producing a list of the form which Nuitka wants.<\/p>\n<p><strong>Fix:<\/strong><\/p>\n<p>in \/usr\/lib\/python3.6\/site-packages\/nuitka\/utils\/SharedLibraries.py<\/p>\n<p>add the following code immediately after the import statements at the top:<\/p>\n<blockquote>\n<p>def find_alpine(name,paths):<br \/>&nbsp;&nbsp;&nbsp;&nbsp; for path in paths:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for root, dirs, files in os.walk(path):<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if name in files:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return os.path.join(root,name)<\/p>\n<\/blockquote>\n<p>and in locateDLL, before import subprocess, add the following code:<\/p>\n<blockquote>\n<p>if os.path.isfile(&#8216;\/etc\/alpine-release&#8217;):<br \/>&nbsp;&nbsp;&nbsp;&nbsp; return find_alpine(dll_name,[&#8220;\/lib&#8221;,&#8221;\/usr\/lib&#8221;,&#8221;\/usr\/local\/lib&#8221;])<br \/>\nelse:<br \/>&nbsp;&nbsp;&nbsp;&nbsp; import subprocess<\/p>\n<p>&nbsp;&nbsp;&nbsp; (\u2026)<\/p>\n<\/blockquote>\n<p>The (\u2026) above indicates that the remainder of the code of locateDLL should be indented under the else.<\/p>\n<p>What this code does: if the file \/etc\/alpine-release exists \u2013 indicating that we are running on Alpine Linux, we search for the shared library in three predefined directories:<\/p>\n<ul>\n<li>\/lib<\/li>\n<li>\/usr\/lib<\/li>\n<li>\/usr\/local\/lib<\/li>\n<\/ul>\n<p>and return the first matching file. <strong>Note, we do not handle the situation in case the file is not found \u2013 in this case nothing is returned<\/strong>.<\/p>\n<p><\/p>\n<p><strong><\/strong><b>Second error<\/b><\/p>\n<blockquote>\n<p>Traceback (most recent call last):<br \/>&nbsp;&nbsp; File &#8220;\/usr\/lib\/python3.6\/site-packages\/nuitka\/__main__.py&#8221;, line 188, in &lt;module&gt;<br \/>&nbsp;&nbsp;&nbsp;&nbsp; main()<br \/>&nbsp;&nbsp; File &#8220;\/usr\/lib\/python3.6\/site-packages\/nuitka\/__main__.py&#8221;, line 182, in main<br \/>&nbsp;&nbsp;&nbsp;&nbsp; MainControl.main()<br \/>&nbsp;&nbsp; File &#8220;\/usr\/lib\/python3.6\/site-packages\/nuitka\/MainControl.py&#8221;, line 859, in main<br \/>&nbsp;&nbsp;&nbsp;&nbsp; standalone_entry_points = standalone_entry_points<br \/>&nbsp;&nbsp; File &#8220;\/usr\/lib\/python3.6\/site-packages\/nuitka\/freezer\/Standalone.py&#8221;, line 1169, in copyUsedDLLs<br \/>&nbsp;&nbsp;&nbsp;&nbsp; used_dlls = detectUsedDLLs(source_dir, standalone_entry_points)<br \/>&nbsp;&nbsp; File &#8220;\/usr\/lib\/python3.6\/site-packages\/nuitka\/freezer\/Standalone.py&#8221;, line 1062, in detectUsedDLLs<br \/>&nbsp;&nbsp;&nbsp;&nbsp; assert os.path.isabs(dll_filename), dll_filename<br \/>\nAssertionError: ldd<\/p>\n<p><\/p>\n<\/blockquote>\n<p>Reason behind the error: nuitka calls ldd with each shared library (\u201cdll_filename\u201d) to be added to your .dist folder. The purpose of this is to find the dependencies of these shared libraries themselves. (A recursive search if you will).<\/p>\n<p>For example:<\/p>\n<blockquote>\n<p>pidoctor:\/opt\/pidoctor# ldd \/usr\/lib\/libexpat.so.1<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ldd (0x76f43000)<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libgcc_s.so.1 =&gt; \/usr\/lib\/libgcc_s.so.1 (0x76efd000)<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; libc.musl-armhf.so.1 =&gt; ldd (0x76f43000)<\/p>\n<\/blockquote>\n<p>As you see, libexpat.so.1 has two dependencies:<\/p>\n<ul>\n<li>libgcc_s.so.1 \u2013&gt; to be found at \/usr\/lib\/libgcc_s.so.1<\/li>\n<li>libc.musl-armhf.so.1 \u2013&gt; to be found at <strong>ldd<\/strong><\/li>\n<\/ul>\n<p>and this is precisely where nuitka has problems. It wants to copy a file with an absolute path, and is given the name \u201cldd\u201d instead. Again, something which is specific to Alpine Linux.<\/p>\n<p>Therefore the assertion fails, and nuitka does not continue.<\/p>\n<p>Here\u2019s how to find out which file it actually references:<\/p>\n<blockquote>\n<p>\npidoctor:~# which ldd<br \/>\n\/usr\/bin\/ldd<\/p>\n<p>pidoctor:~# ls -alh \/usr\/bin\/ldd<br \/>\nlrwxrwxrwx&nbsp;&nbsp;&nbsp; 1 root&nbsp;&nbsp;&nbsp;&nbsp; root&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 28 Jan&nbsp; 1&nbsp; 1970 \/usr\/bin\/ldd -&gt; ..\/..\/lib\/ld-musl-armhf.so.1<\/p>\n<\/blockquote>\n<p>Note that ..\/..\/lib\/ld-musl-armhf.so.1 is a relative path, which actually refers to <strong>\/lib\/ld-musl-armhf.so.1 <\/strong>on my system. And on yours, probably, too.<\/p>\n<p><strong>Fix:<\/strong><\/p>\n<p>search for <strong>libutil.so. <\/strong>in the file \/usr\/lib\/python3.6\/site-packages\/nuitka\/freezer\/Standalone.py<\/p>\n<p>There is a line with result.add(filename) below it. Just <strong>above <\/strong>this line, insert:<\/p>\n<blockquote>\n<p>if filename == &#8216;ldd&#8217;: <br \/>&nbsp;&nbsp;&nbsp;&nbsp; filename = os.path.join(os.path.dirname(&#8216;\/usr\/bin\/ldd&#8217;),os.readlink(&#8216;\/usr\/bin\/ldd&#8217;)) #e.g. &#8216;\/lib\/ld-musl-armhf.so.1&#8217; fix for Alpine\n<\/p>\n<\/blockquote>\n<p><strong>Third error<\/strong><\/p>\n<p>Error, needs &#8216;chrpath&#8217; on your system, due to &#8216;RPATH&#8217; settings in used shared<\/p>\n<p><strong>Fix:<\/strong><\/p>\n<p>Did you install chrpath as advised above? <\/p>\n<blockquote>\n<p>apk add chrpath<\/p>\n<\/blockquote>\n<p>That\u2019s it \u2013 now nuitka should perform it\u2019s magic without hiccups. I will send the author of nuitka the patches and this blog article, maybe he can include them in nuitka.<\/p>\n<h1>Factory version<\/h1>\n<p>Update 2.2.2019: The author of Nuitka added my patches (in a slightly modified form). In the current \u201cfactory\u201d version, the \u2013standalone compilation works for me. Please refer to this github page for details:<\/p>\n<p><a href=\"https:\/\/github.com\/Nuitka\/Nuitka\/issues\/237\">https:\/\/github.com\/Nuitka\/Nuitka\/issues\/237<\/a><\/p>\n<p><a href=\"http:\/\/nuitka.net\/doc\/factory.html\">http:\/\/nuitka.net\/doc\/factory.html<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Motivation pidoctor ist in Python geschrieben (da es keinen einfachen Weg gab, Crystal unter ARMHF \/ musl zum Laufen zu bringen). Dies bedeutet eine Abh\u00e4ngigkeit von Python - was einen zus\u00e4tzlichen Overhead bedeutet. Ich vermute, dass dieser Overhead der Grund daf\u00fcr ist, dass pidoctor nicht auf 256 MB Raspberry Pi's l\u00e4uft - weil der RAM ersch\u00f6pft ist...<\/p>","protected":false},"author":830,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[401,402],"tags":[512,435,509,510,514,515,508,516,513,517,511],"class_list":["post-7312","post","type-post","status-publish","format-standard","hentry","category-alpine-linux","category-development","tag-standalone","tag-alpine-linux","tag-armhf","tag-compile-python","tag-cpython","tag-ldd","tag-libmusl","tag-libuuid-so-1-3-0","tag-nuitka","tag-pip","tag-python-standalone"],"_links":{"self":[{"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/posts\/7312","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/users\/830"}],"replies":[{"embeddable":true,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/comments?post=7312"}],"version-history":[{"count":4,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/posts\/7312\/revisions"}],"predecessor-version":[{"id":7386,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/posts\/7312\/revisions\/7386"}],"wp:attachment":[{"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/media?parent=7312"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/categories?post=7312"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/tags?post=7312"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}