Apologies for the late reply, I’ve been playing around with solutions all week and trying to understand the issue myself! Perhaps the language I’ve used is a bit sloppy or inaccurate, I’ll give the actual code I’m using as an example and try to explain the problem based off of that; I’ve also placed the solution I came up with below, but I invite any criticism of the method as I’m not sure it’s the best way to go about it myself! My original code is as follows…
fw_dir = <pathname of a directory to be used as the argument to the '-output' option of the external process>
completed_process = run(['./payload-dumper-go', '-output', fw_dir, '-partitions', <partitions string>, './opflash/payload.bin'], stdout=PIPE, text=True)
and later in the script…
firmware_images = [<just a list of 20 strings, each one being the name of a firmware .img file>]
partitions = [<another list of 20 strings, each one being an element of firmware_images with the .img suffix removed>]
for i in range(len(firmware_images))
completed_process = run(['fastboot', 'flash', '--slot=all', firmware_images[i], partitions[i], '2>&1'], stdout=PIPE, text=True)
as far as I can tell, the above code (written for a Linux OS) is not portable for two reasons:
- Linux pathname elements are separated by a forward slash, Windows a backwards slash. The first code snippet above is intended to invoke payload-dumper-go from the current working directory, however on Windows this would require
.\payload-dumper-go
. I now realise the constant os.sep
contains the character used to separate pathname elements on the current platfrom and could be used as follows (note that this is made redundant by the solution at the bottom of this post):
completed_process = run([('.' + os.sep + 'payload-dumper-go'), '-output', fw_dir, '-partitions', <partitions string>, ('.' + os.sep + 'opflash' + os.sep + 'payload.bin')], stdout=PIPE, text=True)
- In relation to the second code snippet, on a Linux machine the program ‘fastboot’ will most likely be installed via the package manager and will be located in a directory that is included in the user’s $PATH (probably /usr/bin) so it will work. On a Windows machine the fastboot binary will most likely have been downloaded from a website into the users Downloads\ directory and will need to be moved into the Python interpreters current working directory OR a directory that is included in the Windows variable %PATH%, in order to be invoked using the program name alone. Alternatively I suppose I could have my script search for the fastboot binary and then invoke it using it’s full pathname.
In the end I went for the following solution of using shutil.which() to check if the binary is contained within a directory on the user’s PATH and, if not, using os.walk to recurse through the directory tree rooted at the users home directory and search for a a file of the same name as the respective binary for which the user has execute permission. If a file is found then it is assumed to be the binary/executable, and it’s containing directory is added to the users PATH:
### imports ###
from shutil import which
from os import access, X_OK
from os.path import expanduser, join, pathsep
found_binaries = {'payload-dumper-go':False,'fastboot':False}
for binary in ['payload-dumper-go','fastboot']:
### if the value returned by shutil.which() is truthy, binary is on PATH ###
if which(binary):
found_binaries[binary] = True
else:
for dirpath, _, filenames in walk(expanduser('~')):
for filename in filenames:
### if a file with the correct name for which the user has execute permission is found, add it's containing diretory to PATH and break from the loop ###
if filename == binary and access(join(dirpath, filename), X_OK):
found_binaries[binary] = True
environ['PATH'] += (pathsep + dirpath)
break
### if a matching file has been found, do not process the next directory/tuple returned by os.walk()
if found_binaries[binary] == True:
break
By making sure that the required binaries are on the user’s PATH, the call to subprocess.run() may refer to the programs using their name alone and is not required to include a relative or absolute pathname for them.