Search code examples
pythonpyinstallercythonsetup.pycythonize

Hiding python source code to prevent people from seeing it.. The whole and proper way


I have a few Python files, and I want to pack them into an exe and distribute commercially, but also make sure that no one is able to see the source code of the files... I have heard about pyarmor module, but it does not provide full obfuscation.. Any help regarding this?

I have heard stuff about Cython and Pyinstaller, but never got it to work. I have seen many posts on how to first convert the code to C source code, and then compile it to exe but none of those worked for me.. Anyone here willing to tell me how can I achieve the same?


Solution

  • So we will be using a few modules to -->

    --> Convert our Python code to C source code and PYD files (PYD is the Python equivalent of DLL files)

    --> Pack them into an exe


    The modules we will be needing are -->

    --> Cython

    --> Pyinstaller


    We will be using my project files (as an example), to demonstrate how to convert all the files to C source code and PYD files

    The files in my project are -->

    --> chat_screen.py

    --> constants.py

    --> main_app.py

    --> rooms_list_screen.py


    1. We will make another folder, and name it Distributable executable files (You can name it whatever u want)

    2. We will be adding all the Python files in the folder but change the extension of all the files from py to pyx

    3. Then make another file, known as setup.py and add the following code to it (Note the extension of that file should be py and not pyx)


    The setup.py file:

    from distutils.core import setup
    from distutils.extension import Extension
    from Cython.Distutils import build_ext
    from Cython.Build import cythonize
    
    ext_modules = [
        Extension("chat_screen", ["chat_screen.pyx"]),
        Extension("constants", ["constants.pyx"]),
        Extension("rooms_list_screen", ["rooms_list_screen.pyx"]),
        Extension("main_app", ["main_app.pyx"])
    ]
    setup(name='My Cython App',
          cmdclass={'build_ext': build_ext},
          ext_modules=cythonize(ext_modules),
          compiler_directives={'language_level': 3},
          zip_safe=False
          )
    

    So what we are doing here is, using the Extension class to make all the files, an extension of the app. So the format for the same would be Extension("The name by which u r importing the file (mostly the file name)", ["The full file name with its pyx extension"]

    Then we are making a setup function and specifying the list of the extensions we made earlier in the ext_modules kwarg.. But, we have to wrap the list inside of the cythonize function to compile all the files into C source code and pyd files.


    1. Now, open a command prompt window in the same folder and run this command python setup.py build_ext --inplace and wait. This will output some C and PYD files.. These are your Python files, now compiled.

    2. Now, make another file named main.py (Note the extension of that file should be py and not pyx) and add this code to it __import__("main_app") (Replace the file name with your main file), and then run it simply. If your script works without any errors, that means you are ready to compile your app into an exe!!

    3. Now, install Pyinstaller with pip install pyinstaller and in the same folder, open a command prompt and run pyinstaller main.py and wait.

    4. You will see a spec file in the same directory and a build and dist folder. We can ignore both of those folders for now.

    5. Then, open the spec file and you will see something like this in it


    block_cipher = None
    
    
    a = Analysis(['main.py'],
                 pathex=['Omitted due to privacy concerns'],
                 binaries=[],
                 datas=[],
                 hiddenimports=[],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher,
                 noarchive=False)
    pyz = PYZ(a.pure, a.zipped_data,
                 cipher=block_cipher)
    exe = EXE(pyz,
              a.scripts,
              [],
              exclude_binaries=True,
              name='main',
              debug=False,
              bootloader_ignore_signals=False,
              strip=False,
              upx=True,
              console=True )
    coll = COLLECT(exe,
                   a.binaries,
                   a.zipfiles,
                   a.datas,
                   strip=False,
                   upx=True,
                   upx_exclude=[],
                   name='main')
    

    In the datas kwarg of the Analysis function, add the following code

    [
                     ("chat_screen.c", "."),
                     ("chat_screen.cp39-win_amd64.pyd", "."),
                     ("constants.c", "."),
                     ("constants.cp39-win_amd64.pyd", "."),
                     ("main_app.c", "."),
                     ("main_app.cp39-win_amd64.pyd", "."),
                     ("rooms_list_screen.c", "."),
                     ("rooms_list_screen.cp39-win_amd64.pyd", ".")
                 ]
    

    So what we are doing here is, adding every C and pyd file that was generated in the spec file, to make sure they make into the compiled exe. The . represents that the files should be kept in the root of the exe directory.

    Also, add the list of imports you are doing in your script in the hiddenimports kwarg of the Analysis function, which in my case is ['requests', 'kivy'].. So my spec file looks something like this

    # -*- mode: python ; coding: utf-8 -*-
    block_cipher = None
    
    
    a = Analysis(['main.py'],
                 pathex=['Omitted due to privacy concerns'],
                 binaries=[],
                 datas=[
                     ("chat_screen.c", "."),
                     ("chat_screen.cp39-win_amd64.pyd", "."),
                     ("constants.c", "."),
                     ("constants.cp39-win_amd64.pyd", "."),
                     ("main_app.c", "."),
                     ("main_app.cp39-win_amd64.pyd", "."),
                     ("rooms_list_screen.c", "."),
                     ("rooms_list_screen.cp39-win_amd64.pyd", ".")
                 ],
                 hiddenimports=['requests', 'kivy'],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher,
                 noarchive=False)
    pyz = PYZ(a.pure, a.zipped_data,
                 cipher=block_cipher)
    exe = EXE(pyz,
              a.scripts,
              [],
              exclude_binaries=True,
              name='main',
              debug=False,
              bootloader_ignore_signals=False,
              strip=False,
              upx=True,
              console=True )
    coll = COLLECT(exe,
                   a.binaries,
                   a.zipfiles,
                   a.datas,
                   strip=False,
                   upx=True,
                   upx_exclude=[],
                   name='main')
    
    

    1. Now, in a command prompt window in the same folder, type in pyinstaller main.spec and press Enter.. Now your files are being converted to an exe!!! Just wait for some time now, and when the process is finished, just go to the following directory in the same folder dist\<name>\ and you will find all the files of your app here. If you want to launch the app, look for a main.exe file and run it.. There, your app is now ready!!!

    If you want to make it into a single file, then instead of just doing pyinstaller main.spec, do pyinstaller main.spec --onefile and it would make a single executable file in the same directory!!!

    Don't use UPX with Pyinstaller in this case, as it resulted in some random DLL errors in my case, which got fixed after I repacked without using UPX