I am trying to create a python setup script that will be converted to an executable file in windows using pyinstaller. Currently, I am trying to figure out how this script would check if python is installed on the system, if not, then it will go ahead & download the installation exe file from the python website.
My current code is as follows:
import subprocess, requests, platform
architecture = platform.uname()[4]
if architecture == 'AMD64':
executable_file = "https://www.python.org/ftp/python/3.11.1/python-3.11.1-amd64.exe"
elif architecture == 'ARM64':
executable_file = "https://www.python.org/ftp/python/3.11.1/python-3.11.1-arm64.exe"
r = requests.get(executable_file)
with open("python-3.11.1-amd64.exe",'wb') as f:
f.write(r.content)
cmd = "python-3.11.1-amd64.exe"
returned_value = subprocess.call(cmd, shell=True)
if returned_value == 1602:
print("Installation Cancelled. Exitting.")
quit()
print('returned value:', returned_value)
I currently have no idea how to check if python is installed so the exe will not be downloaded & ran by the script. So I would like some ideas on how I could go with this.
Directly execute the python command to see the output, or use the winreg module to obtain the installed applications of the local machine
Related
Created an executable using pyinstaller on ubuntu 16.04 and trying to run it on SuSe 12 SP4 gives error at a certain portion of the code.
The code works like this:
Its a flask app that receives the input from user over web
Process those inputs and creates a .sh script and run that shell script
Reads the output from the shell script and present it to the web as a return render
The executable was successfully created on the ubuntu machine and works successfully and no issues seens but when I use this executable on SuSe12 SP4, it starts but when it reaches the code where it runs the bash script, it throws the following error:
sh: /tmp/_MEI369vhy/libreadline.so.6: no version information available (required by sh)
I am really tired of looking for solutions and have done the following so far:
Tried both --onefile and --onedir, no difference
Tried creating the executable on SuSe12 sp4 itself but it throws a different error regarding subprocess not being found
Tried finding the libreadline.so on Suse with no luck
Tried creating an env on ubuntu 14 but too many dependencies errors
I'm finally out of suggestions and could use some help here. If you can please assist.
Environment
Python 2.7.12
Ubuntu 16.04
SuSe12 SP4
Pyinstaller 3.6
P.S. The code as a raw python code works flawlessly on SuSe 12 SP4 if i create proper build environment
So, I finally solved it with some assistance from Rokm. The warning message above was not causing any issues but it was due to the environment variable not being passed to subprocess.
In order to solve this issue, I simply did the following:
###Add the following code to your existing code
env = dict(os.environ) # make a copy of the environment
lp_key = 'LD_LIBRARY_PATH' # for GNU/Linux and *BSD.
lp_orig = env.get(lp_key + '_ORIG')
if lp_orig is not None:
env[lp_key] = lp_orig # restore the original, unmodified value
else:
# This happens when LD_LIBRARY_PATH was not set.
# Remove the env var as a last resort:
env.pop(lp_key, None)
Next, add the env variable to subprocess Popen command. Here is the full code for reference. This code will give you the output of the command and also return code aka exit code of the command. Also, you don't have to use any shelix or any other things to run it, simple .strip() command will do it for you. Hope you guys find it useful, enjoy. !!
from subprocess import Popen,PIPE,STDOUT
env = dict(os.environ) # make a copy of the environment
lp_key = 'LD_LIBRARY_PATH' # for GNU/Linux and *BSD.
lp_orig = env.get(lp_key + '_ORIG')
if lp_orig is not None:
env[lp_key] = lp_orig # restore the original, unmodified value
else:
# This happens when LD_LIBRARY_PATH was not set.
# Remove the env var as a last resort:
env.pop(lp_key, None)
cmd = raw_input('Enter your command:')
out = Popen(cmd.split(),stderr=STDOUT,stdout=PIPE, env=env)
t, y = out.communicate()[0],out.returncode
print('output : ' + str(t))
print ('Return Code : ' + str(y))
P.S. Unfortunately, cmd.split() will fail in some cases, ie when an argument has spaces, like cmd='/usr/bin/ls "/home/user/my directory"' will fail with cmd.split(). In that case, cmd = shlex.split(cmd, posix=True) will work better. But shlex.split() will fail when stdout is captured, hence there is no allrounder solution IMHO
I have a python script that uses the Pynput module. When I run the python script from terminal on Ubuntu [20.04LTS GUI] it runs perfectly.
$ pyinstaller --onefile vTwo.py
cd ./dist
./vTwo
Error occurs when running ./script:
ImportError: this platform is not supported: No module named 'pynput.keyboard._xorg'
Try one of the following resolutions:
* Please make sure that you have an X server running, and that the DISPLAY environment variable is set correctly
[5628] Failed to execute script vTwo
If someone could advise me on what may be going wrong. I have had a look at the Pynput requirements page where they mention that it requires X server to be running in the background which should not be an issue as I have a GUI installed.
Also is there anyway to use Pynput on a system without a gui?
The Solution
The solution is easy. Just include this module as a hidden import in the PyInstaller program:
python -m PyInstaller your_program.py --onefile --hidden-import=pynput.keyboard._xorg
If you also use the mouse with pynput, then you'll get the same error with the module pynput.mouse._xorg. So do this:
python -m PyInstaller your_program.py --onefile --hidden-import=pynput.keyboard._xorg --hidden-import=pynput.mouse._xorg
Warning! You'll likely get a different module that it doesn't find, if you're packaging for Windows or Mac. This is what you get for Linux. If you want your program to be cross-platform, then you'll have to package the program, for example, for Windows and test it to see which module it doesn't find and include it as a hidden import.
For example, if you want your program to work on Linux and Windows, use this command:
python -m PyInstaller your_program.py --onefile --hidden-import=pynput.keyboard._xorg --hidden-import=pynput.mouse._xorg --hidden-import=pynput.keyboard._win32 --hidden-import=pynput.mouse._win32
If you have a lot of hidden modules, then you may edit the .spec file and add the modules to the hiddenimports list like so (on PyInstaller 4.1):
hiddenimports=['pynput.keyboard._xorg', 'pynput.mouse._xorg'],
Why The Error
When you see an ImportError in a Python program packaged by PyInstaller, there is a high chance that the problem is that PyInstaller couldn't detect that specific import and didn't include it in the binary, hence the import error.
In the error message it tells you which module it didn't find:
ImportError: this platform is not supported: No module named 'pynput.keyboard._xorg'
which is pynput.keyboard._xorg, because you're on Linux.
It couldn't find the module, because it was imported in a "non-traditional" way. Look at the source code for pynput/_util/__init__.py in the backend function:
def backend(package):
backend_name = os.environ.get(
'PYNPUT_BACKEND_{}'.format(package.rsplit('.')[-1].upper()),
os.environ.get('PYNPUT_BACKEND', None))
if backend_name:
modules = [backend_name]
elif sys.platform == 'darwin':
modules = ['darwin']
elif sys.platform == 'win32':
modules = ['win32']
else:
modules = ['xorg']
errors = []
resolutions = []
for module in modules:
try:
return importlib.import_module('._' + module, package)
except ImportError as e:
errors.append(e)
if module in RESOLUTIONS:
resolutions.append(RESOLUTIONS[module])
raise ImportError('this platform is not supported: {}'.format(
'; '.join(str(e) for e in errors)) + ('\n\n'
'Try one of the following resolutions:\n\n'
+ '\n\n'.join(
' * {}'.format(s)
for s in resolutions))
if resolutions else '')
You can see that it uses the import_module function from the importlib module to import the correct module for the platform. This is why it couldn't find the
pynput.keyboard._xorg module.
Your Second Question
Also is there anyway to use Pynput on a system without a gui?
I don't know.
I need to download and install a program with the python code, I figured out how to download, however, there are problems in the installation process. The code is the following
import platform
import os
import wget
url_windows='https://download.oracle.com/otn-pub/java/jdk/13.0.1+9/cec27d702aa74d5a8630c65ae61e4305/jdk-13.0.1_windows-x64_bin.exe'
url_mac='https://download.oracle.com/otn-pub/java/jdk/13.0.1+9/cec27d702aa74d5a8630c65ae61e4305/jdk-13.0.1_osx-x64_bin.dmg'
url_linux='https://download.oracle.com/otn-pub/java/jdk/13.0.1+9/cec27d702aa74d5a8630c65ae61e4305/jdk-13.0.1_linux-x64_bin.deb'
if platform.system() == 'Windows':
installer=wget.download(url_windows)
if platform.system()== 'Linux':
installer=wget.download(url_linux)
if platform.system() == 'Mac':
installer=wget.download(url_mac)
path=os.path.abspath(installer)
f=open(path)
As in the path the username consists of whitespace and the error occurs.
You should use os.system(path) or os.startfile(path) to launch an executable. The open command opens a file and returns a python object, which is not what you want in this case.
I'm running on Windows Server 2012 R2 standard machine with 32Bit python.
I'm trying to run a powershell script for a 64Bit system which check if a key exists in the registry:
$path = "Registry::HKLM\Software\<folder>"
if (!(Test-Path $path))
{
write "$path does not exist!"
}
When running through powershell, script works perfect.
When I'm running it from python, it doesn't find the key:
from gevent.subprocess import call
call("powershell <script.ps1>, shell=True")
After some research, I found that the 32bit python process is calling the 32bit version of powershell.
I verified it with this simple script that checks if the process running is 32bit or 64bit:
powershell [System.Environment]::Is64BitProcess
Will return True for 64Bit process and False for a 32Bit process.
Manually checking this command works:
C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe [System.Environment]::Is64BitProcess
Returns False as this is the 32Bit version of powershell (yes, quite confusing due to the WOW64 folder).
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe [System.Environment]::Is64BitProcess
Returns True.
But running:
from gevent.subprocess import call
call("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe [System.Environment]::Is64BitProcess", shell=True)
Returns False
What am I missing?
What you're missing is that Windows transparently redirects file system calls from 32-bit processes to C:\Windows\System32 to C:\Windows\SysWOW64 :)
There a simple solution to this - if a 32-bit process tries to fetch file system resources from C:\Windows\sysnative instead, Windows will not redirect to SysWOW64.
From PowerShell it's fairly easy to figure out whether your process will be affected by all of this SysWOW64 redirection, basically:
$IsSysWOW64Process = [Environment]::Is64BitOperatingSystem -and -not [Environment]::Is64BitProcess
In python however, I haven't been able to find a surefire way of doing this, so I suspect your best bet is to call kernel32!IsWow64Process2() via ctypes:
from ctypes import windll,c_ushort,byref
import platform
def is_syswow64_process():
if platform.architecture()[0] != '64bit':
# 32-bit OS, no syswow64 handling
return False
# Ok, 64-bit OS, let's see if the process is 32-bit
# Obtain process handle to self
this_process = windll.kernel32.GetCurrentProcess()
# Declare ref arguments
proc_type, platform_type = c_ushort(), c_ushort()
# Query Windows for the information
wow64_call = windll.kernel32.IsWow64Process2(this_process, byref(proc_type), byref(platform_type))
if wow64_call == 0:
# you'd probably want to call kernel32!GetLastError here
raise Exception("Problem querying kernel32!IsWow64Process2")
return proc_type.value == 0
And now you can conditionally replace the path when needed:
powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
if is_syswow64_process():
powershell = re.sub("(?i)syswow64|system32", "sysnative", powershell)
call("%s .\path\to\script.ps1" % powershell)
A script is developed both on OS X and Windows using a virtualenv. The so-called developer has already installed all required packages using a requirements.txt file, but one problem remains:
If the script is running on OS X, the beginning of each Python file must start like this:
#!/Users/os-x-username/.virtualenvs/some_env/bin/python
#!C:\Users\windows-username\Envs\some_env\Scripts\python.exe
But if developing on Windows, the line order must be switched:
#!C:\Users\windows-username\Envs\some_env\Scripts\python.exe
#!/Users/os-x-username/.virtualenvs/some_env/bin/python
How can the so-called developer avoid this tediousness?
If you don't mind adding extra steps, ou can create a launcher script launcher.py like:
#!/usr/bin/env python
import subprocess
import sys
if __name__ != "__main__":
print("This is a launcher. Please run only as a main script.")
exit(-1)
INTERPRETERS = {
"win": r"C:\Users\windows-username\Envs\some_env\Scripts\python.exe", # Windows
"darwin": "/Users/os-x-username/.virtualenvs/some_env/bin/python", # OSX
"linux": "/home/linux-user/.virtualenvs/some_env/bin/python" # Linux
# etc.
}
TARGET_SCRIPT = "original_script_name.py"
interpreter = None
for i in INTERPRETERS: # let's find a suitable interpreter for the current platform
if sys.platform.startswith(i):
interpreter = i
break
if not interpreter:
print("No suitable interpreter found for platform:", sys.platform)
exit(-1)
main_proc = subprocess.Popen([interpreter, TARGET_SCRIPT] + sys.argv[1:]) # call virtualenv
main_proc.communicate() # wait for it to finish
exit(main_proc.poll()) # redirect the return code
Since this script is there only to run the original_script_name.py in the desired interpreter for the current platform, it doesn't matter what its shebang is - as long as it picks any Python interpreter it will be fine.
It would act as a drop-in replacement for your original script (original_script_name.py) so just call launcher.py instead and it will even redirect the CLI arguments if needed.