Low Level Classes And Functions

distbuilder logo

Stand Alone Executables

buildExecutable

To build a stand-alone binary distribution of a Python program, invoke the buildExecutable function:

buildExecutable( name=None, entryPointPy=None,
                 pyInstConfig=PyInstallerConfig(), 
                 opyConfig=None, 
                 distResources=[], distDirs=[] )

Returns (binDir, binPath): a tuple containing: the absolute path to the package created, the absolute path to the binary created (within the package).

name: The (optional) name given to both the resulting executable and package distribution directory. This argument is only applied if pyInstConfig is None. If omitted, the pyInstConfig attribute for this is used.

entryPointPy: The (optional) path to the Python script where execution begins. This argument is only applied if pyInstConfig is None. If omitted, the pyInstConfig attribute for this is used.

pyInstConfig: An (optional) PyInstallerConfig object to dictate extended details for building the binary using the PyInstaller Utility. If omitted, the name and entryPointPy arguments, plus other default settings will be used.

opyConfig: An (optional) OpyConfig object, to dictate code obfuscation details using the Opy Library. If omitted (or explicitly specified as None), no obfuscation will be performed. See Executable Obfuscation.

distResources: An (optional) list of external resources to bundle into the distribution package containing the binary. You may use a simple list of strings containing file/directory names or paths relative to the build script directory. Else, you may provide a list of two element tuples, with a specific source and destination. The source may be a relative or absolute path from another location on your file system. The destination maybe whatever name/path you want specified relative to the package being created. Note that the default destination path is the root directory of the package, using the same file name. To package a resource within a sub directory, or with an alternate name, you must either explictly provide a full (relative) destination path or use the "shortcut value" True to indicate the source and destination are the same relative paths.

distDirs: An (optional) list of directories to create within the package. Note distResources implicitly does this for you when there is a source to copy. This additional option is for adding new empty directories.

makePyInstSpec

To generate a PyInstaller .spec file, using the secondary utility "pyi-makespec" (bundled with PyInstaller), invoke the makePyInstSpec function:

makePyInstSpec( pyInstConfig, opyConfig=None )

**Returns: the absolute path to the spec file created. Also, updates the pyInstConfig argument, supplying a PyInstSpec object and effectively returning it "by reference".

pyInstConfig: An PyInstallerConfig object used to dictate the details for generating the spec file using the makespec Utility.

opyConfig: An (optional) OpyConfig object, providing supplemental details regarding the spec file creation. Be sure to include this if you desire obfuscation and will be subsequently invoking the buildExecutable function.

Installers

Upon creating a distribution (especially a stand-alone executable), the next logical progression is to bundle that into a full-scale installer. This library is designed to employ the open source, cross platform utility: Qt IFW (i.e. "Qt Installer Framework") for such purposes. While the prototypical implementation of that tool is with a Qt C++ program, it is equally usable for a Python program (especially if using "Qt for Python", and a QML driven interface...).

installQtIfw

When the QtIFW utility is required for use by the library (i.e. when buildInstaller is invoked), an attempt will be made to resolve the path to it via a collection of methods. First, if a QtIfwConfig object is provided which specifies a path via the qtIfwDirPath attribute, that will be employed. Second, if there is an environmental variable defined named QT_IFW_DIR, that path will be applied. Next, a default directory where the utility would typically be installed will be checked to see if that exists. If none of these found, distbuilder will simply download the program an install it for you in that "default" location, using the download function and this installQtIfw function.

If you wish to take control of such yourself, or simply download QtIFW directly for some other purpose, this function provides the means.

installQtIfw( installerPath=None, version=None, targetPath=None )

Returns: the absolute path to the directory where the utility was successfully installed.

installerPath: (Optional) The path to the QtIFW installer to be run. This may either be a local path or a url to a location on the web. If omitted, the version argument will be used to dynamically resolve the url.

version: (Optional) The version of QtIFW desired. This should be provided as a string e.g. "3.1.1". If installerPath is omitted, this argument will be used to dynamically resolve the url. If this argument is also omitted, a default version will be selected automatically.

targetPath: (Optional) The target directory for installation. If omitted, a default path will be used.

unInstallQtIfw

This is the counterpart to the installQtIfw() function. It will uninstall an existing installation of the QtIFW utility.

unInstallQtIfw( qtIfwDirPath=None, version=None )

Returns: a boolean to indicate success or failure.

qtIfwDirPath: (Optional) The absolute path to the directory where the utility was installed. (i.e. the return value of installQtIfw()). If omitted, the version argument will be looked to next.

version: (Optional) The version of QtIFW to uninstall. If qtIfwDirPath is omitted, this argument will be used to dynamically resolve the default install path to the utility for the version specified. If this argument is also omitted, a default version will be assumed.

buildInstaller

buildInstaller( qtIfwConfig, isSilent )

Returns: the absolute path to the setup executable created.

qtIfwConfig: A (required) QtIfwConfig object which dictates the details for building an installer.
The distbuilder library allows you either define a QtIFW installer in the full/natural manner, and then drawn upon that resource, or it can generate one from entirely from scratch, or any combination thereof is possible. Setting the attribute installerDefDirPath, indicates that the installer definition (or at least part of it) already exists and is to be used. Dynamic components can be defined via attributes for the nested objects of type QtIfwConfigXml, QtIfwControlScript, QtIfwPackage, QtIfwPackageXml, and QtIfwPackageScript.
Other key attributes include the pkgName, which is the sub directory where your content will be dynamically copied to within the installer, and the pkgSrcDirPath (most typically the binDir returned by buildExecutable), which is source path of the content.

isSilent: When isSilent is enabled, the QtIFW installer produced will not display a GUI or provide any interactive prompts for the user. All options are dictated by command line arguments. While this may certainly be desirable on any platform, it is
necessary to create an installer for a target OS with no GUI (e.g. many Linux distros).

See Silent Installers for more information.

Standard Installer Arguments

The standard (non silent) QtIFW installer can accept a collection of command line arguments out of the box. To see the available options, simply pass the switch -h or --help when executing the binary from a terminal.

The distbuilder library, has added a series of additional options as well (assuming the built-in default Control Script additions are preserved). Unfortunately, there does not appear to be a way to add these to the QtIFW help! Note these differ from the Silent Installer Arguments. The following custom arguments must be passed in the format "Key=Value":

errlog=[path]: The path where you would like (custom) error details written to for installation debugging. The default is <temp dir>/installer.err.

target=[path]: The target directory for the installation.

startmenu=[relative path]: (Windows only) The target start menu directory for shortcuts.

install=[ids]: The full set of components to include in the installation, represented as a space delimited list of package ids. Those ids NOT listed, will not be installed (unless some custom logic added to the Control script forces them to be). This list takes priority over any include or exclude arguments.

include=[ids]: The components to additionally include in the installation, represented as a space delimited list of package ids. Default components do NOT need to be listed here, as they are already "included" implicitly.

exclude=[ids]: The components to exclude from in the installation, represented as a space delimited list of package ids. Only default components need to be listed here, those not automatically included they are already "excluded" implicitly.

accept=[true/false]: Enable the check box for accepting the end user license agreement.

run=[true/false]: Enable the check box for running the target program at the end of the installation.

onexist=[fail/remove/prompt]: Define the action taken when an existing (conflicting) installation is present. The default is to prompt, and allow for automatic removal if the user so chooses explicitly.

mode=[addremove/update/removeall]: This option is provided for controlling how the "Maintenance Tool" is to be run.

auto=[true/false]: Enable "Auto pilot" mode. This is very much akin to a running the installation like a Silent Installer, and is in fact at the heart of how that works. Unlike a silent installer, GUI suppression is involved, however, and some of the extended features are not available e.g. error return codes.

Silent Installers

"Silent installation" is the process of running a installer without any interactive prompts for the user. It simply runs without "talking" to you. All options are dictated by command line arguments. This allows for scripted installs of programs, which is useful for a great many purposes, e.g. programmatic integrations of external utilities, or running the same installation on a large number of workstations. While such a feature can be desirable on any platform, not requiring a GUI interaction is, of course, outright necessary if one wishes to target an OS with no GUI support (e.g. many Linux distros).

"Silent installation" (and/or no GUI) is not a option provided naturally by the Qt Installer Framework. This feature has been made available, however, via this library! As such, it is now easily possible to build installers for environments that would not normally be able via the otherwise excellent installation system Qt has given us.

Note that in addition to those major feature additions, a distbuilder silent installer also returns an error code (1) upon failure. A natural QtIFW installer does not, returning a "success code" (0) even when the installation fails.

Silent Installer Arguments

One of the core features of Silent Installers is that they can be driven by command line arguments. The following switches have been provided for this type of distbuilder installer. Note these differ from the Standard Installer Arguments.

-h / --help: Display help for these arguments.

-v / --verbose: Enable verbose output.

-f / --force: "Force" installation. Uninstall an existing installation automatically, in the event that there is a conflict. Without this, the installer would abort under such conditions by default (per the natural QtIFW design).

-t / --target [path]: The target directory for the installation.

-m / --startmenu [relative path]: (Windows only) The target start menu directory for shortcuts.

-c / --components [ids]: The full set of components to include in the installation, represented as a space delimited list of package ids. Those ids NOT listed, will not be installed (unless some custom logic added to the Control script forces them to be). This list takes priority over any include or exclude arguments.

-i / --include [ids]: The components to additionally include in the installation, represented as a space delimited list of package ids. Default components do NOT need to be listed here, as they are already "included" implicitly.

-e / --exclude [ids]: The components to exclude from in the installation, represented as a space delimited list of package ids. Only default components need to be listed here, those not automatically included they are already "excluded" implicitly.

The component selecting arguments are only made available when more than one package is defined for the installer. If there is only a single package, there is not a needed to select/de-select it! When these arguments can be used, the --help text will list the package ids and indicate if they are included by default. As package ids are defined in the "Java package" format (e.g. "com.company.product"), and typically installers will all have the same long form prefix for all such ids, that prefix will be truncated for the user's benefit. So, rather than "com.company.product", the id will become simply "product". If any of the packages do not have the same prefix as the rest, they will all be be listed in the long manner.

Installer Variables

The following constants have been provided, which correspond to dynamic variables resolved at runtime by QtIFW. Note these are applicable for BOTH direct Installer Script generation, and as parameters and attributes for many higher level functions and objects in this library.

QT_IFW_OS

QT_IFW_TARGET_DIR

QT_IFW_ROOT_DIR

QT_IFW_HOME_DIR 
QT_IFW_DESKTOP_DIR

QT_IFW_APPS_DIR 
QT_IFW_APPS_X86_DIR
QT_IFW_APPS_X64_DIR

QT_IFW_STARTMENU_DIR
QT_IFW_USER_STARTMENU_DIR
QT_IFW_ALLUSERS_STARTMENU_DIR

QT_IFW_INSTALLER_DIR 
QT_IFW_INTALLER_PATH

QT_IFW_PRODUCT_NAME 
QT_IFW_PRODUCT_VERSION 
QT_IFW_TITLE 
QT_IFW_PUBLISHER 
QT_IFW_URL

Note: use joinPathQtIfw to build paths with such constants.

Installer Scripting

While both QtIFW, and the distbuilder additions to it, provide many build-in features for customizing installers, nothing can provide more open ended flexibility than writing your own scripts.

QtIFW scripts are written in Qt Script (which is conceptually a spin off from JavaScript), with additional custom objects and methods for this context. To truly understand it and learn about all of it's features in detail, you should refer to the official QtIFW Manual.

The classes QtIfwControlScript and QtIfwPackageScript provide abstraction layers for QtIfw script generation. With these classes you can achieve a great many custom behaviors, driven by scripts, without having to learn much about the language yourself.

Both of the distbuilder script classes provide the following PYTHON helpers:

Static Constants :

TAB         
NEW_LINE     
END_LINE    
START_BLOCK 
END_BLOCK

TRUE  
FALSE

PATH_SEP

MAINTENANCE_TOOL_NAME

VERBOSE_CMD_SWITCH_ARG     
TARGET_DIR_KEY         
PRODUCT_NAME_KEY

ERR_LOG_PATH_CMD_ARG   
ERR_LOG_DEFAULT_PATH

TARGET_DIR_CMD_ARG        
START_MENU_DIR_CMD_ARG        
ACCEPT_EULA_CMD_ARG       
INSTALL_LIST_CMD_ARG      
INCLUDE_LIST_CMD_ARG      
EXCLUDE_LIST_CMD_ARG      
RUN_PROGRAM_CMD_ARG       
AUTO_PILOT_CMD_ARG

TARGET_EXISTS_OPT_CMD_ARG 
TARGET_EXISTS_OPT_FAIL    
TARGET_EXISTS_OPT_REMOVE  
TARGET_EXISTS_OPT_PROMPT

MAINTAIN_MODE_CMD_ARG        
MAINTAIN_MODE_OPT_ADD_REMOVE 
MAINTAIN_MODE_OPT_UPDATE         
MAINTAIN_MODE_OPT_REMOVE_ALL

OK     
YES     
NO     
CANCEL

Static Functions:

log( msg, isAutoQuote=True )            
debugPopup( msg, isAutoQuote=True )
errorPopup( msg, isAutoQuote=True )

setValue( key, value, isAutoQuote=True )               
lookupValue( key, default="", isAutoQuote=True )            
lookupValueList( key, defaultList=[], isAutoQuote=True, 
                 delimiter=None )

getEnv( varName, isAutoQuote=True )

killAll( exeName, isAutoQuote=True )

targetDir()
productName()

cmdLineArg( arg, default="" )
cmdLineSwitchArg( arg )
cmdLineListArg( arg, default=[] )
ifCmdLineArg( arg, isNegated=False, isMultiLine=False, )  
ifCmdLineSwitch( arg, isNegated=False, isMultiLine=False )

ifInstalling( isMultiLine=False )
ifMaintenanceTool( isMultiLine=False )

fileExists( path, isAutoQuote=True )
ifFileExists( path, isAutoQuote=True, isMultiLine=False )

yesNoPopup( msg, title="Question", resultVar="result" )             
yesNoCancelPopup( msg, title="Question", resultVar="result" )                  
switchYesNoCancelPopup( msg, title="Question", resultVar="result", 
                        onYes="", onNo="", onCancel="" )
ifYesNoPopup( msg, title="Question", resultVar="result", 
             isMultiLine=False )

Dir.toNativeSparator( path )

_autoQuote( value, isAutoQuote )

In addition, QtIfwControlScript provides:

Static Constants :

NEXT_BUTTON 
BACK_BUTTON 
CANCEL_BUTTON
FINISH_BUTTON

TARGET_DIR_EDITBOX       
START_MENU_DIR_EDITBOX   
ACCEPT_EULA_RADIO_BUTTON 
RUN_PROGRAM_CHECKBOX

Static Functions:

pageWidget( name )     
customPageWidget( name ): 
currentPageWidget()                
assignPageWidgetVar( pageName, varName="page" ):                           
assignCustomPageWidgetVar( pageName, varName="page" ):                
assignCurPageWidgetVar( varName="page" ):

enable( controlName, isEnable=True )

setVisible( controlName, isVisible=True )

getText( controlName )
setText( controlName, text, isAutoQuote=True )

clickButton( buttonName, delayMillis=None )

    (Note: check box controls also work on radio buttons)    
setCheckBox( checkboxName, isCheck=True ):

If writing scripts directly for distbulder integration, you may also employ the following add-on QT SCRIPT functions:

execute( binPath, args )

sleep( seconds )

killAll( progName )

writeFile( path, content ) <path can include native env vars>
deleteFile( path )         <path can include native env vars>

clearErrorLog()
writeErrorLog( msg )

quit( msg )
abort( msg )
silentAbort( msg )

targetExists( isAutoPilotMode )
defaultTargetExists()
cmdLineTargetExists()

removeTarget( isAutoPilotMode )

maintenanceToolExists( dir )
toMaintenanceToolPath( dir )

<Windows Only>   
    maintenanceToolPaths()  <resolves via registry lookups>
    isOsRegisteredProgram() 
    executeVbScript( vbs )

    <Package Context Only>
        addVbsOperation( component, isElevated, vbs )
        setShortcutWindowStyleVbs( shortcutPath, styleCode )

<Linux Only>        
    isAptInstalled() 
    isDpkgInstalled()
    isYumInstalled()
    isRpmInstalled()
    isPackageManagerInstalled( prog )

    isPackageInstalled( pkg )
    installPackage( pkg ) 
    unInstallPackage( pkg )

QtIfwPackage list manipulation

These functions are useful within an overridden version of onPyToBinPkgsBuilt in a RobustInstallerProcess, object

findQtIfwPackage( pkgs, pkgId )

Returns: QtIfwPackage object within the pkgs argument with the id supplied by pkgId.

removeQtIfwPackage( pkgs, pkgId )

Removes the QtIfwPackage within the pkgs argument with the id supplied by pkgId.

mergeQtIfwPackages( pkgs, srcId, destId )

Merges the QtIfwPackage objects within the pkgs argument with the ids supplied by srcId and destId.

"Merging" first of all entails a recursive content merge of the source into the target via mergeDirs. In addition, a number of other configuration details will be "merged" as well. Examples of such include combining the lists of QtIfwShortcut objects, QtIfwExternalOp objects, and the customOperations script snipets nested inside the QtIfwPackageScript objects, followed by script regeneration to reflect such. Note that all attributes of the source package, which aren't explictly handled by the library in this operation, are lost! Some further customizations to the result may need to be made post merge for a given use case.

If the source package has a subDirName attribute, that detail will be preserved by this nesting. I.e. the merge will retain the sub directory encapsulation.

This function ultimately consolidates the package items in the list and returns the destination object.

nestQtIfwPackage( pkgs, childId, parentId, subDirName=None )

"Nests" the "child" QtIfwPackage object within the "parent", modifying the pkgs collection supplied.

"Nesting" entails moving the child content into a sub directory of the parent as well as combining the QtIfwShortcut list nested inside the QtIfwPackageScript objects, followed by script regeneration to reflect that. Note that (most) other attributes of the source package are lost! Exceptions to that would be dynamically generated content, e.g. wrapper scripts, which were already added to the content. Other customizations which need to be made must be applied post merge.

If a subDirName value is provided, that will be used to name the nested directory. If omitted, the child's package name will be used (or a truncated version of that if the child and parent share a common package name "prefix"). If the source package has a subDirName attribute, that detail will be overridden by this nesting. I.e. this will NOT nest the content two levels deep.

This function ultimately consolidates the package items in the list and returns the destination object.

After all package manipulation has been completed, it is recommended that you call genQtIfwCntrlRes. This will often help to apply assorted modifications per the new configuration, avoiding the need to do so via additional "manual" coding.

Code Obfuscation

Code obfuscation is the process of rewriting normal, human readable code, into a form which is very difficult (well, ideally impossible) to read, yet still executes in exactly the same manner when run through the target translator (or compiler). The reason one would want to do this to is to protect proprietary work and/or to eliminate security holes (based upon context) while sharing source code.

Opy for Distribution Builder is an obfuscation library for Python. It can be used for protecting source that will be shared directly, or as an additional layer of protection behind binaries built with PyInstaller.

While Opy does not protect your code as well as real compilation does, it's far better than nothing! The obfuscation process is "lossy", so while the end result still functions as desired, Opy inherently destroys the clear text original names for functions, classes, objects, etc. A hacker might still figure out some "secret" after putting in a good deal of effort, but no one can't just walk away with your body of the work on the whole, or instantly spot your security secrets.

Executable Obfuscation

Why would you need to obfuscate when building executable binaries? Aren't they implicitly protected by virtue to being compiled?

Well, the thing is you don't literally "compile" when using PyInstaller (or Py2Exe, etc.)!
The mechanism for creating standalone Python executables works by creating .pyc files and bundling them with a Python interpreter into a neat package. Unfortunately .pyc can be reverse engineered back into the original (or nearly original) .py source.

As a quick starting point to learn about this hacking process, you can check out these Stack Overflow posts:

If you don't trust posts from "random" third parties, simply read this - straight from the horse's mouth: PyInstaller Docs: Hiding The Source Code

One "solution" to this problem is to bundle .pye files instead of .pyc. PyInstaller provides built-in support for this, in fact. The alternate .pye is an encrypted equivalent to a .pyc. If your end user is in possession of the decryption key, they can run the code, because it will be unlocked for them to begin the process of running it. Unfortunately, that still ultimately exposes the code! It merely restricts access. If your goal is to distribute a program, for which you want to prevent access to the source, then you couldn't distribute the key! So, this security measure is only really pertinent when it is possible for someone to access the program independently from the key, and the target user being given the key can be trusted with the raw source. That's a fairly atypical use case...

obfuscatePy

To generate an obfuscated version of your project (without converting it to binary) invoke obfuscatePy:

obfuscatePy( opyConfig )

Returns (obDir, obPath): a tuple containing: the absolute path to the obfuscated package, the absolute path to the obfuscated entry point script (within the package).

opyConfig: An OpyConfig object, which dictates the code obfuscation details using the Opy Library.

Upon invoking this, you will be left with an "obfuscated" directory adjacent to your build script. This is a useful preliminary step to take, prior to running buildExecutable, so that you may inspect and test the obfuscation results before building the final distribution package.

Library Obfuscation

obfuscatePyLib

To generate an obfuscated version of your project, which you can then distribute as an importable library, invoke obfuscatePyLib:

obfuscatePyLib( opyConfig, 
                isExposingPackageImports=True, 
                isExposingPublic=True )

Returns (obDir, setupPath): a tuple containing: the absolute path to the obfuscated directory, the absolute path to the (non obfuscated) setup.py script within the prepared package

opyConfig: An OpyConfig object, to dictate code obfuscation details using the Opy Library.

isExposingPackageImports: Option to NOT obfuscate any of the imports defined in the package entry point modules (i.e. init.py files). This is the default mode for a library.

isExposingPublic: Option to NOT obfuscate anything which it is naturally granting public access (e.g. module constants, functions, classes, and class members). All locals and those identifiers prefixed with a double underscore (denoting private) will be still be obfuscated.
This is the default mode for a library.

Obfuscation Features

The Opy Library contains an OpyConfig class, which has been extended by the distbuilder library (and aliased with the same name).
The revised / extended class contains attributes for patching the obfuscation and for bundling the source of external libraries (so that they too maybe obfuscated). This new configuration type has the notable additions:

patches
bundleLibs 
sourceDir

OpyPatch

Opy is not perfect. It has known bugs, and can require a bit of effort in order to define a "perfect" configuration for it. In the event you are struggling to make it work exactly as desired, an "easy out" has been provided by way of "patching" the results. If you have already determined exactly which files/lines/bugs you are encountering, you may simply define a list of "OpyPatch" objects for the configuration. They will be applied at the end of the process (i.e. to the obfuscated code) to fix any problems. An OpyPatch is created via:

OpyPatch( relPath, patches, parentDir=OBFUS_DIR_PATH )

relPath: The relative file path within the obfuscation results that you wish to change.

patches: A list of tuples. 2 element tuples in the form (line number, line) will be utilized for complete line replacements. Alternatively, 3 element tuples in the form (line number, old, new) will perform a find/replace operation on that line (to just swap out an identifier typically).

parentDir: An (optional) path to use a directory other than the standard obfuscation results path.

LibToBundle

When an OpyConfig object is created, the sourceDir defaults to THIS_DIR. If the bundleLibs attribute is defined, it is used in combination with sourceDir to create a "staging directory". The bundleLibs attributes may be either None or a list of
"LibToBundle" objects, constructed via:

LibToBundle( name, localDirPath=None, pipConfig=None, 
             isObfuscated=False )

That class has attributes named likewise, which may be set after creating such an object as well.

name: The name of the library, i.e. the name to be given to the bundled package.

localDirPath: If this library may be simply copied from a local source, this is the path to that source. Otherwise, leave this as None.

pipConfig: A PipConfig object defining how to download and install the library. The destination will be automatically set to the "staging directory" for the obfuscation process.

isObfuscated: A boolean indicating if the library is already obfuscated, and therefore may be bundled as is.

createStageDir

In the event that defining bundleLibs for an OpyConfig object will not suffice to setup your staging directory, you may instead call:

createStageDir( bundleLibs=[], sourceDir=THIS_DIR )

Returns: the path to the newly created staging directory.

After doing this, you may perform any extended operations that you require, and then set an OpyConfig object's sourceDir to that staging path while leaving bundleLibs as None in the configuration.

Note that the OpyConfig external_modules list attribute must still be set in such a manner to account for the libraries which were bundled, or which remain as "external" imports.

Library Installation

installLibrary

To install a library (via pip), invoke installLibrary. Note: that installLibraries (plural) may used to install multiple libraries in a single call.

installLibrary( name, opyConfig=None, pipConfig=PipConfig() )

Returns: None

name: The name/source of the library. If the library is your current project itself and you are obfuscating it, be sure to supply the name you are giving it. If you are NOT obfuscating it, specify "." instead. If you wish to install a remote package registered with pip (i.e. the typical way pip is used), simply supply the name. If you wish to use a local path, or a specific url (http/git), see PipConfig (and perhaps the pip documentation for details).

opyConfig: An (optional) OpyConfig object, to dictate code obfuscation details using the Opy Library. If omitted, no obfuscation will be performed. If you are building an obfuscated version of your project as a importable library, this function is useful for testing the operations of your library post-obfuscation/pre-distribution. This will
run obfuscatePyLib with default arguments, install the library, and remove the temporary obfuscation from the working directory.

pipConfig: An (optional) PipConfig object, to dictate extended installation details. If omitted, the library is simply installed in the standard manner to your (global) Python site packages. Notable attributes include incDpndncsSwitch, destPath and asSource. These allow you to skip dependency gathering if desired, install to a specific path such as a temp build directory, and to request raw .py scripts be placed there. Note that remote raw pip packages will require an alternate "vcs url" be supplied to a "development" repository in place of the simple package name.
See editable installs

installLibraries

installLibraries( *libs )

This function is a convenience wrapper over installLibrary (singular) function. One of the primary uses for this function is to ensure that the product to be created by a build script is being run in an environment which has all of its dependencies.

Returns: None

*libs: This function is extremely flexible in terms of how it may be invoked. The libs argument maybe a tuple, a list, or a series of arguments passed directly to the function of arbitrary length. The arguments or collection may consist of simple strings (i.e. the library names), or be tuples / dictionaries themselves. When passing tuples, or dictionaries, they will be treated as the argument list to installLibrary().

Simple example:

installLibraries( 'six', 'abc' )

Simple example with a version detail:

installLibraries( 'six', 'abc', 'setuptools==40.6.3' )

Complex example:

opyCfg = OpyConfig()
...     
pipCfg = PipConfig()
...     
myLib = {'name':'MyLib', 'opyConfig':opyCfg, 'pipConfig':pipCfg }   
installLibraries( 'six', myLib )

uninstallLibrary

uninstallLibrary( name )

Returns: None

Simply uninstalls a library from Python site packages in the basic/traditional pip manner.

vcsUrl

vcsUrl( name, baseUrl, vcs="git", subDir=None )

Convenience function to build vcsUrls from their component parts. This is to be used in conjunction with the PipConfig attribute asSource. See https://pip.pypa.io/en/stable/reference/pip_install/#vcs-support

Testing

run

Upon creating a binary with PyInstaller, use the following to test the success of the operation:

run( binPath, args=[], 
     wrkDir=None, isElevated=False, isDebug=False )

binPath: The path to the binary to be executed. Note that the path is returned by buildExecutable, which allows the results of that to flow directly into this function.

args: An (optional) list of arguments, (or a flat string) to pass along to your program.

wrkDir: An (optional) working directory specification. If omitted (or None), the working directory will be automatically set to that of the binary path specified.

isElevated: Boolean (option) to run the binary with elevated privileges.

isDebug: Boolean (option) for explicitly displaying standard output and standard error messages. Set this to True to debug a PyInstaller binary which was created with pyInstConfig.isGui set to True. On some platforms, when that configuration is used, messages sent to the console (e.g. print statements or uncaught exceptions) are not visible even when launching the application from a terminal. Enabling this option, however, will expose those messages to you. This can be invaluable for debugging problems that are unique to a stand-alone binary, and not present when run in the original raw script form. For instance, it is common for PyInstaller binaries to be missing dependencies which must be accounted for (e.g. via "hidden imports"). In such situations, exceptions maybe thrown when the app launches. Without this debugging feature, you may have no information regarding the fatal error.

Note: When run employees the debugging feature, it will set an environmental variable named DEBUG_MODE to 1. Some contexts require this in order to allow this mode to work correctly. You may wish to include your own custom logic within your program to pivot on this environmental condition as well. In case you wish to avoid hard-coded references for these checks, the library includes the following constants:

DEBUG_ENV_VAR_NAME
DEBUG_ENV_VAR_VALUE

You may also wish to include and employ this function in your program, which checks for this condition.

from os import environ    
def isDebug(): 
    try: return isDebug.__CACHE
    except:
        isDebug.__CACHE = environ.get("DEBUG_MODE")=="1"
        return isDebug.__CACHE

Note: In some contexts, you will NOT see the debugging output until the executable has terminated. On Windows, this will occur when using debug mode in combination with isElevated enabled, if you are NOT already running as an admin. On macOS, this will occur whenever using a "wrapper script" (see QtIfwExeWrapper) over the binary.

Note: some IDE / platform combinations may render this feature inoperable due to a conflict with output stream handling (e.g. Eclipse/PyDev on Windows), in which case simply execute the build script, or the run function, from a terminal outside of the IDE when employing isDebug.

runPy

Perhaps most notable, upon creating a Python obfuscation, you may wish the to test the success of that operation. The following was provided with that in mind specifically:

runPy( pyPath, args=[], isElevated=False )

pyPath: The path to the script to be executed. Note that the path is returned by obfuscatePy, which allows the results of that to flow directly into this function.

args: An (optional) list of arguments, (or a flat string) to pass along to your program.

isElevated: Boolean (option) to run the binary with elevated privileges.

The working directory will be automatically set to the directory of the python script.

Testing installers

The following two options are available for a QtIFW installer:

1) Manually run the installer with the "-v" switch argument. Or, if running it via this library using the run(...) function, you can pass the QT_IFW_VERBOSE_SWITCH constant as an argument.

2) If the build process is failing before you can run the installer, try setting qtIfwConfig.isDebugMode to True for verbose output (note this should currently be the be the default now).

Archives and Distribution

Once you have a fully built distribution package, the following functions provide an easy means for further preparing the program for distribution:

toZipFile

toZipFile( sourceDir, zipDest=None, removeScr=True )

Returns: the path to the zip file created.

sourceDir: the directory to convert to a zip (typically the binDir).

zipDest: (optional) full path to the zip file to be created. If not specified, it will be named with same base as the source, and created adjacent to it.

removeScr: Option to delete the sourceDir after creating the zip of it. Note this is the default behavior.

TODO: Add git commit/push...

ExecutableScript

Executable scripts have wide ranging potential for use with this library.
They may be employed as part of the build process, or deployed with a distribution.

The ExecutableScript class is used to generate / bundle such scripts. By default, this is a batch file on Windows, or a shell script on Linux or Mac.

The class QtIfwExeWrapper) often contains a class of his type, used as a "wrapper" over a deployed executable. In some contexts, that wrapper mechanism is implicitly employed by deployment preparing tools the library leans on, and/or is added directly by distbuilder.

Class PyInstHook) is a derived class from ExecutableScript. Note that it is a Python script rather than the default type for a given platform. This derivation is a good example of where you would want to the read function and the line parsing/building functions.

Constructor:

ExecutableScript( rootName, 
                  extension=True, shebang=True,                   
                  script=None, scriptPath=None )

Attributes & default values:

rootName <required> 
extension=True  # i.e. automatic
shebang=True    # i.e. automatic   
script=None
scriptPath=None

Functions:

fileName()

read( dirPath  )
write( dirPath )

toLines()        
fromLines( lines )
injectLine( injection, lineNo )

debug()

Details:

rootName: The name of script without the extension. If this is used as an "exe/binary wrapper", this name should normally align with the root of that binary's name, which may be acquired with rootFileName( path ).

extension: The file extension used when creating the file. If True, the extension will be automatically assigned. This defaults to bat on Windows, sh on Mac and Linux. If set to None, there will be no extension on the file. A user supplied string will be applied if custom provided.

shebang: A "shebang" injected into the top of the script automatically. On Windows, this attribute is not used. Else, if True, #!/bin/sh will be used. A user supplied string will be applied if custom provided.

script: The content for the for script, provided as a string.

scriptPath: The content for the for script, provided as a file path to source for where it is to be extracted.

Utilities

The following low level "utilities" are also provided for convenience, as they maybe useful in defining the build parameters, further manipulating the package, or testing the results, etc.

absPath

THIS_DIR

The path to the directory which contains the build script.

absPath( relativePath, basePath=None )

Covert a relative path to an absolute path. If a basePath is not specified, the path is re resolved relative to THIS_DIR (which may or MAY NOT be the current working directory).

joinPathQtIfw

Use this to build paths which will be utilized by QtIFW scripts (directly or indirectly) on a target machine. The paths will be joined (and used) in a platform agnostic manner.

Note, use joinPath to build paths in a platform specific manner, applicable to where a build script will be employing it to create a distribution.

isParentDir

isParentDir( parent, child, basePath=None ):

Returns: true/false, the parent / child paths specified, exist and have such a relationship to one another. The paths maybe relative or absolute. basePath is optionally used for relative paths. To actually get the parent directory, use dirPath.

Copy or Move To Dir

copyToDir( srcPaths, destDirPath )

Returns: the destination path(s) to the file(s) / directory(ies).

Copies files OR directories to a given destination. The argument srcPaths may be a singular path (i.e. a string) or an iterable (i.e. a list or tuple). This replaces any existing copy found at the destination path. When relative paths are specified, they are resolved via absPath.

moveToDir( srcPaths, destDirPath )

Moves files OR directories to a given destination. The argument srcPaths may be a singular path (i.e. a string) or an iterable (i.e. a list or tuple). (Note: it moves the path specified, it does not leave a copy of the source). This replaces any existing copy found at the destination path. When relative paths are specified, they are resolved via absPath.

copyToDesktop( path )            
moveToDesktop( path )
copyToHomeDir( path )   
moveToHomeDir( path )

Convenience wrapper functions which directly imply the destination.

removeFromDir

removeFromDir( subPaths, parentDirPath )

Removes files OR directories from a given directory. The argument subPaths may be a singular path (i.e. a string) or an iterable (i.e. a list or tuple). A subPath argument must be relative to the parentDirPath. When relative paths are specified for parentDirPath, they are resolved via absPath.

renameInDir

renameInDir( namePairs, parentDirPath )

Renames files OR directories with a given directory. The argument namePairs may be a singular tuple (oldName, newName) or an iterable (i.e. a list or tuple) of such tuple pairs. When relative paths are specified for parentDirPath, they are resolved via absPath.

collectDirs

collectDirs( srcDirPaths, destDirPath )

Moves a list of directories into a common parent directory. That parent directory will be created is it does not exist. When relative paths are specified or parentDirPath, they are resolved via absPath.

mergeDirs

mergeDirs( srcDirPaths, destDirPath, isRecursive=True )

Move the contents of a source directory into a target directory, over writing the target contents where applicable. If performed recursively, the destination contents contained within a merged sub directory target are all preserved. Otherwise, the source sub directories replace the target sub directories as whole units. When relative paths are specified, they are resolved via absPath.

normBinaryName

normBinaryName( path, isPathPreserved=False, isGui=False )

The "normalized" name of a binary, resolving such for cross platform contexts. On Windows, binaries normally end in a ".exe" extension, but on other platforms they normally have no extension. On macOS, binaries to be launched with a GUI, normally have a ".app" extension (vs none when they do not have a GUI). That additional logic is applied when isGui is True. When isPathPreserved is True, the entire path is returned rather than only the file name. When False (the default) a full path is stripped down to the base name.

normIconName

normIconName( path, isPathPreserved=False )

The "normalized" name of an icon, resolving such for cross platform contexts. On Windows, icons end in a ".ico" extension, on macOS ".icns" files are used. In Linux, there is no fixed standard exactly on icons, since many distros are non-gui, and as such Linux binaries do not have icons embedded in them. For Linux desktops, however, it is common place to use external ".png" files to create icons which point to binaries. When isPathPreserved is True, the entire path is returned rather than only the file name. When False (the default) a full path is stripped down to the base name.

getEnv, setEnv, delEnv

getEnv( varName, default=None )
setEnv( varName, value )
delEnv( varName )

Use these functions to retrieve or manipulate environmental variables.

versionTuple, versionStr

versionTuple( ver, parts=4 )    
versionStr( ver, parts=4 )

These functions return "version representations" as either tuples of integers, or as strings delimited by periods respectively (e.g. "1.0.0.0"). Based upon context, either format is commonly used by this library, and elsewhere.

The ver argument may take many forms: a string (Unicode or bytes), an integer, a float, a tuple, a list... It must simply use digits for each "part" of the version. Alpha characters are not permitted.

The optional parts argument will truncate or pad the return value, so it has that many elements present in the representation. 4 "parts" is the standard, i.e. "Major.Minor.Micro.Build".

versionNo, assertMinVer, assertBuilderVer

versionNo( ver, parts=4, partLen=3 )
assertMinVer( ver, minVer, parts=4, partLen=3, descr=None )
assertBuilderVer( ver )

Like versionTuple and versionStr, versionNo takes "any" representation of a version on under the sun and returns an integer. In addition to specifying the number of parts in the version, it will be very important to use a valid and persistent partLen spec. That is the maximum number of digits to allow for use in each part. This factor exponentially changes the numeric result from this function.

The function assertMinVer is provided to raise an exception in the event of a version (of whatever form and context), not meeting the requirements for the build process to continue.

For convenience, assertBuilderVer is provided to confirm the minimum version of this library. It may be useful to start some build scripts in a manner resembling the following:

from distbuilder assertBuilderVer
assertBuilderVer( "0.7.8.0" )

Module import utilities

modulePath( moduleName )

The absolute path to an importable module's source. (Note the moduleName argument should be a string, not an unquoted, direct module reference.)
This is useful for dynamically resolving the path to external modules which you may wish to copy / "bundle" for obfuscation. Returns None if the name is invalid and/or the path cannot be resolved. Note that this often resolves the path to a library's package entry point (i.e. an init.py) file where the module is initially imported, rather than literal module path. Normally modulePackagePath will be more useful...

modulePackagePath( moduleName )

Similar to modulePath, but this return the module's parent directory. More often than not, a module will have dependencies within the package / library where it resides. As such, resolving the package path can be more useful than the specific module.

sitePackagePath( packageName )

Similar to modulePackagePath, but takes the package name rather than a module within it AND is specific to the site packages collection of libraries, rather than a more universal path resolution.

isImportableModule( moduleName )
isImportableFromModule( moduleName, memberName )

Attempts the import, and returns a boolean indication of success without raising an exception upon failure.
Like the related functions here, the arguments are expected to be strings (not direct references).
The purpose of this to test for library installation success, or to preemptively confirm the presence of dependencies.

importFromPath( path, memberName=None )

Imports a module, or a select member from it, via the explicit path to that script, and returns the reference. Example:

myFunc = importFromPath( "/path/to/myscript.py", "myFunc" )
myFunc( someArg )

This can be very useful for cross project integrations where you want to import modules, or members of them, which are not part of an installed system library or if they are located in a path where a standard import cannot be employed directly.

halt

halt()

Immediately stops execution of the script. This can be useful for debugging, since it is typical to the use library for auto generating and purging files which you might want to inspect along the way.

printErr, printExc

printErr( msg, isFatal=False )

Roughly emulates the print command, but writes to stderr. Optionally, exits the script with a return code of 1 (i.e. general error).

printExc( e, isDetailed=False, isFatal=False )

Analogous to printErr, but prints an exception's more detailed repr() information. Optionally, prints a stack trace as well when isDetailed=True. Note: use printErr( e ) to print just an exception "message".

download

download( url, saveToPath=None, preserveName=True )

Returns: the local path to the completed download.

url: The url to the file that is to be downloaded.

saveToPath: (Optional) The full local path where the file should be downloaded to. If omitted (or set to None), this path will be automatically assigned to one within a temp directory.

preserveName: (Optional) If saveToPath is None, this boolean dictates whether the original name of the file should be used when saving the file locally. If set to False, the name will be auto assigned to one which does not conflict with any that already exist. If set to True, and the path already exists, the new download will overwrite the prior file.

Aliased standard python functions

    exists                 os.path.exists 
    isFile                 os.path.isfile or os.path.islink 
    isDir                  exists AND not isFile
    copyFile               shutil.copyFile 
    removeFile             os.remove
    makeDir                os.makedirs
    copyDir                shutil.copytree     
    removeDir              shutil.rmtree
    move                   shutil.move
    rename                 os.rename
    tempDirPath            tempfile.gettempdir()    
    rootFileName            <custom> os.path.splitext of basename 
    baseFileName           os.path.basename         
    dirPath                os.path.dirname
    joinPath               os.path.join
    splitPath              os.path.split
    splitExt               os.path.splitext 
    joinExt                 <custom> inverse of splitExt

General Purpose Constants

IS_WINDOWS 
IS_LINUX 
IS_MACOS

PY2
PY3

BIT_CONTEXT
IS_32_BIT_CONTEXT
IS_64_BIT_CONTEXT

THIS_DIR

DEBUG_ENV_VAR_NAME
DEBUG_ENV_VAR_VALUE