VirtualBox

Changeset 103028 in vbox for trunk/src/VBox/Installer/common


Ignore:
Timestamp:
Jan 24, 2024 3:53:59 PM (13 months ago)
Author:
vboxsync
svn:sync-xref-src-repo-rev:
161263
Message:

Main/Python: Big revamp to modernize our vboxapi Python package to now use a so-called src-layout, which also can be used with pip directly. Also added compatibility w/ Python 3.12 where distutils are not shipped anymore. We also now do have automatic linting for our code, which hopefully should improve quality in this area. Moved some Python-related files into an own sub folder so that it's more clear to which these belong to. The "sdk/installer" directories also have an own "python" sub directory where the stuff resides now. The Windows installer also uses "sdk/installer" instead of "sdk/install", to match the other platforms. bugref:10579

Location:
trunk/src/VBox/Installer/common
Files:
1 added
2 edited
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Installer/common/Config.kmk

    r102983 r103028  
    11# $Id$
    22## @file
    3 # kBuild Configuration file for VBoxShell.
     3# kBuild Configuration file for common installer parts.
    44#
    55
     
    2626#
    2727
    28 VBOX_VBOXSHELL_CONFIG_KMK_INCLUDED = 1
     28VBOX_INSTALLER_COMMON_CONFIG_KMK_INCLUDED = 1
    2929
    3030# Include the top-level configure file.
     
    3636# Globals
    3737#
    38 VBOX_PATH_VBOXSHELL_SRC := $(PATH_ROOT)/src/VBox/Frontends/VBoxShell
     38VBOX_PATH_INSTALLER_COMMON_SRC := $(PATH_ROOT)/src/VBox/Installer/common
    3939
    4040#
    4141# List of python sources that should be linted.
    4242#
    43 VBOX_VBOXSHELL_PYTHON_SOURCES    :=
    44 VBOX_VBOXSHELL_PYLINT_TARGETS    :=
     43VBOX_INSTALLER_COMMON_PYTHON_SOURCES :=
     44VBOX_INSTALLER_COMMON_PYLINT_TARGETS :=
    4545
    4646ifdef VBOX_WITH_PYLINT
     
    5151# Process python sources.
    5252#
    53 if1of ($(KBUILD_TARGET), win os2)
    54  VBOX_PYTHONPATH_VBOXSHELL = $(PYTHONPATH);$(VBOX_PATH_VBOXSHELL_SRC)
    55 else
    56  VBOX_PYTHONPATH_VBOXSHELL = $(PYTHONPATH):$(VBOX_PATH_VBOXSHELL_SRC)
    57 endif
     53VBOX_PYTHONPATH_INSTALLER_COMMON = $(PYTHONPATH):$(VBOX_PATH_INSTALLER_COMMON_SRC)
    5854BLDDIRS += $(PATH_TARGET)/pylint
    5955
    60 define def_vbox_vboxshell_py_check
     56define def_vbox_installer_common_py_check
    6157 $(eval name:=$(basename $(notdir $(py))))
    6258
     
    6662 ifdef VBOX_WITH_PYLINT
    6763        $(QUIET2)$(call MSG_L1,Subjecting $(py) to pylint...)
    68         $(QUIET)$(REDIRECT) -C "$(dir $(py))" -E LC_ALL=C -E PYTHONPATH="$(VBOX_PYTHONPATH_VBOXSHELL)" -- \
    69                 $(VBOX_PYLINT) --rcfile=$(VBOX_PATH_VBOXSHELL_SRC)/pylintrc $$(VBOX_PYLINT_FLAGS) $$($(py)_VBOX_PYLINT_FLAGS) ./$(notdir $(py))
     64        $(QUIET)$(REDIRECT) -C "$(dir $(py))" -E LC_ALL=C -E PYTHONPATH="$(VBOX_PYTHONPATH_INSTALLER_COMMON)" -- \
     65                $(VBOX_PYLINT) --rcfile=$(VBOX_PATH_INSTALLER_COMMON_SRC)/pylintrc $$(VBOX_PYLINT_FLAGS) $$($(py)_VBOX_PYLINT_FLAGS) ./$(notdir $(py))
    7066 endif
    7167        $(QUIET)$(APPEND) -t "$(PATH_TARGET)/pylint/$(name).o"
    7268 TESTING += $(name)-py-phony.o
    73  VBOX_VBOXSHELL_PYLINT_TARGETS    += $(PATH_TARGET)/pylint/$(name).o
    74 endef # def_vbox_vboxshell_py_check
     69 VBOX_INSTALLER_COMMON_PYLINT_TARGETS    += $(PATH_TARGET)/pylint/$(name).o
     70endef # def_vbox_installer_common_py_check
    7571
    7672
    77 define def_vbox_vboxshell_process_python_sources
     73define def_vbox_installer_common_process_python_sources
    7874 if $(words $(_SUB_MAKEFILE_STACK)) <= 0 || "$1" == "FORCE"
    79   $(foreach py, $(VBOX_VBOXSHELL_PYTHON_SOURCES), $(eval $(def_vbox_vboxshell_py_check)))
     75  $(foreach py, $(VBOX_INSTALLER_COMMON_PYTHON_SOURCES), $(eval $(def_vbox_installer_common_py_check)))
    8076 endif
    8177endef
  • trunk/src/VBox/Installer/common/Makefile.kmk

    r98429 r103028  
    3737ifdef VBOX_WITH_PYTHON
    3838
    39  INSTALLS += VBox-python-glue-installer
     39 INSTALLS += VBox-python-glue-vboxapisetup
    4040
    41  VBox-python-glue-installer_INST = $(INST_SDK)installer/
    42  VBox-python-glue-installer_MODE = a+r,u+w
    43  VBox-python-glue-installer_SOURCES = vboxapisetup.py
     41 VBox-python-glue-vboxapisetup_INST = $(INST_SDK)installer/python/vboxapi/
     42 VBox-python-glue-vboxapisetup_MODE = a+r,u+w
     43 VBox-python-glue-vboxapisetup_SOURCES = vboxapisetup.py=>setup.py
     44
     45 INSTALLS += VBox-python-glue-vboxapisetup-stub
     46
     47 VBox-python-glue-vboxapisetup-stub_INST = $(INST_SDK)installer/python/
     48 VBox-python-glue-vboxapisetup-stub_MODE = a+r,u+w
     49 VBox-python-glue-vboxapisetup-stub_SOURCES = vboxapisetup-stub.py=>vboxapisetup.py
     50
     51 #
     52 # Automatically lint common installer Python stuff.
     53 #
     54 if defined(VBOX_WITH_PYLINT) && !defined(VBOX_WITHOUT_AUTO_PYLINT)
     55  OTHERS      += $(PATH_TARGET)/pylintInstallerCommon.run
     56  OTHER_CLEAN += $(PATH_TARGET)/pylintInstallerCommon.run
     57  $(PATH_TARGET)/pylintInstallerCommon.run: \
     58        ${PATH_SUB_CURRENT}/vboxapisetup.py ${PATH_SUB_CURRENT}/vboxapisetup-stub.py
     59        $(QUIET)$(APPEND) -t "$@"
     60 endif
     61
     62 VBOX_INSTALLER_COMMON_PYTHON_SOURCES := $(wildcard $(PATH_SUB_CURRENT)/*.py)
     63
     64 $(evalcall def_vbox_installer_common_process_python_sources,FORCE)
    4465
    4566endif # VBOX_WITH_PYTHON
    4667
    4768include $(FILE_KBUILD_SUB_FOOTER)
    48 
  • trunk/src/VBox/Installer/common/vboxapisetup.py

    r102849 r103028  
    3030"""
    3131
    32 import os,sys
    33 from distutils.core import setup
    34 
    35 def cleanupComCache():
     32#
     33# For "modern" Python packages setuptools only is one of many ways
     34# for installing a package on a system and acts as a pure build backend now.
     35# Using a build backend is controlled by the accompanied pyproject.toml file.
     36#
     37# See: https://packaging.python.org/en/latest/discussions/setup-py-deprecated/#setup-py-deprecated
     38#
     39# PEP 518 [1] introduced pyproject.toml.
     40# PEP 632 [2], starting with Python 3.10, the distutils module is marked as being deprecated.
     41# Python 3.12 does not ship with distutils anymore, but we have to still support Python < 3.12.
     42#
     43# [1] https://peps.python.org/pep-0518/
     44# [2] https://peps.python.org/pep-0632/
     45
     46import atexit
     47import io
     48import os
     49import platform
     50import sys
     51
     52g_fVerbose = True
     53
     54def cleanupWinComCacheDir(sPath):
     55    """
     56    Cleans up a Windows COM cache directory by deleting it.
     57    """
     58    if not sPath:
     59        return
    3660    import shutil
    37     from distutils.sysconfig import get_python_lib
    38     comCache1 = os.path.join(get_python_lib(), 'win32com', 'gen_py')
    39     comCache2 = os.path.join(os.environ.get("TEMP", "c:\\tmp"), 'gen_py')
    40     print("Cleaning COM cache at",comCache1,"and",comCache2)
    41     shutil.rmtree(comCache1, True)
    42     shutil.rmtree(comCache2, True)
    43 
    44 def patchWith(file,install,sdk):
    45     newFile=file + ".new"
    46     install=install.replace("\\", "\\\\")
    47     try:
    48         os.remove(newFile)
    49     except:
     61    sDirCache = os.path.join(sPath, 'win32com', 'gen_py')
     62    print("Cleaning COM cache at '%s'" % (sDirCache))
     63    shutil.rmtree(sDirCache, True)
     64
     65def cleanupWinComCache():
     66    """
     67    Cleans up various Windows COM cache directories by deleting them.
     68    """
     69    if sys.version_info >= (3, 2): # Since Python 3.2 we use the site module.
     70        import site
     71        for sSiteDir in site.getsitepackages():
     72            cleanupWinComCacheDir(sSiteDir)
     73    else:
     74        from distutils.sysconfig import get_python_lib # pylint: disable=deprecated-module
     75        cleanupWinComCacheDir(get_python_lib())
     76
     77    # @todo r=andy Do still need/want this? Was there forever. Probably a leftover.
     78    sDirTemp = os.path.join(os.environ.get("TEMP", "c:\\tmp"), 'gen_py')
     79    cleanupWinComCacheDir(sDirTemp)
     80
     81def patchWith(sFile, sVBoxInstallPath, sVBoxSdkPath):
     82    """
     83    Patches a given file with the VirtualBox install path + SDK path.
     84    """
     85    sFileTemp = sFile + ".new"
     86    sVBoxInstallPath = sVBoxInstallPath.replace("\\", "\\\\")
     87    try:
     88        os.remove(sFileTemp)
     89    except Exception as _:
    5090        pass
    51     oldF = open(file, 'r')
    52     newF = open(newFile, 'w')
    53     for line in oldF:
    54         line = line.replace("%VBOX_INSTALL_PATH%", install)
    55         line = line.replace("%VBOX_SDK_PATH%", sdk)
    56         newF.write(line)
    57     newF.close()
    58     oldF.close()
    59     try:
    60         os.remove(file)
    61     except:
     91    # Note: We need io.open() to specify the file encoding on Python <= 2.7.
     92    try:
     93        with io.open(sFile, 'r', encoding='utf-8') as fileSrc:
     94            with io.open(sFileTemp, 'w', encoding='utf-8') as fileDst:
     95                for line in fileSrc:
     96                    line = line.replace("%VBOX_INSTALL_PATH%", sVBoxInstallPath)
     97                    line = line.replace("%VBOX_SDK_PATH%", sVBoxSdkPath)
     98                    fileDst.write(line)
     99                fileDst.close()
     100            fileSrc.close()
     101    except IOError as exc:
     102        print("ERROR: Opening VirtualBox Python source file '%s' failed: %s" % (sFile, exc))
     103        return False
     104    try:
     105        os.remove(sFile)
     106    except Exception as _:
    62107        pass
    63     os.rename(newFile, file)
    64 
    65 # See http://docs.python.org/distutils/index.html
    66 def main(argv):
    67     vboxDest = os.environ.get("VBOX_MSI_INSTALL_PATH", None)
    68     if vboxDest is None:
    69         vboxDest = os.environ.get('VBOX_INSTALL_PATH', None)
    70         if vboxDest is None:
    71             raise Exception("No VBOX_INSTALL_PATH defined, exiting")
    72 
    73     vboxVersion = os.environ.get("VBOX_VERSION", None)
    74     if vboxVersion is None:
     108    os.rename(sFileTemp, sFile)
     109    return True
     110
     111def testVBoxAPI():
     112    """
     113    Performs various VirtualBox API tests.
     114    """
     115
     116    # Give the user a hint where we gonna install stuff into.
     117    if  g_fVerbose \
     118    and sys.version_info.major >= 3:
     119        import site
     120        print("Global site packages directories are:")
     121        for sPath in site.getsitepackages():
     122            print("\t%s" % (sPath))
     123        print("User site packages directories are:")
     124        print("\t%s" % (site.getusersitepackages()))
     125        print("Module search path is:")
     126        for sPath in sys.path:
     127            print("\t%s" % (sPath))
     128
     129    #
     130    # Test using the just installed VBox API module by calling some (simpler) APIs
     131    # where now kernel drivers are other fancy stuff is needed.
     132    #
     133    try:
     134        from vboxapi import VirtualBoxManager
     135        oVBoxMgr = VirtualBoxManager()
     136        oVBox    = oVBoxMgr.getVirtualBox()
     137        oHost    = oVBox.host
     138        if oHost.architecture not in (oVBoxMgr.constants.PlatformArchitecture_x86, \
     139                                      oVBoxMgr.constants.PlatformArchitecture_ARM):
     140            raise Exception('Host platform invalid!')
     141        print("Testing VirtualBox Python bindings successful: Detected VirtualBox %s (%d)" % (oVBox.version, oHost.architecture))
     142        _ = oVBox.getMachines()
     143        oVBoxMgr.deinit()
     144        del oVBoxMgr
     145    except ImportError as exc:
     146        print("ERROR: Testing VirtualBox Python bindings failed: %s" % (exc))
     147        return False
     148
     149    print("Installation of VirtualBox Python bindings for Python %d.%d successful." \
     150          % (sys.version_info.major, sys.version_info.minor))
     151    return True
     152
     153def findModulePathHelper(sModule = 'vboxapi', aDir = sys.path):
     154    """
     155    Helper function for findModulePath.
     156
     157    Returns the path found, or None if not found.
     158    """
     159    for sPath in aDir:
     160        if g_fVerbose:
     161            print('Searching for "%s" in path "%s" ...' % (sModule, sPath))
     162        if os.path.isdir(sPath):
     163            aDirEntries = os.listdir(sPath)
     164            if g_fVerbose:
     165                print(aDirEntries)
     166            if sModule in aDirEntries:
     167                return os.path.join(sPath, sModule)
     168    return None
     169
     170def findModulePath(sModule = 'vboxapi'):
     171    """
     172    Finds a module in the system path.
     173
     174    Returns the path found, or None if not found.
     175    """
     176    sPath = findModulePathHelper(sModule)
     177    if not sPath:
     178        try:
     179            import site # Might not available everywhere.
     180            sPath = findModulePathHelper(sModule, site.getsitepackages())
     181        except:
     182            pass
     183    return sPath
     184
     185try:
     186    from distutils.command.install import install # Only for < Python 3.12.
     187except:
     188    pass
     189
     190class setupInstallClass(install):
     191    """
     192    Class which overrides the "install" command of the setup so that we can
     193    run post-install actions.
     194    """
     195
     196    def run(self):
     197        def _post_install():
     198            if findModulePath():
     199                testVBoxAPI()
     200        atexit.register(_post_install)
     201        install.run(self)
     202
     203def main():
     204    """
     205    Main function for the setup script.
     206    """
     207
     208    print("Installing VirtualBox bindings for Python %d.%d ..." % (sys.version_info.major, sys.version_info.minor))
     209
     210    # Deprecation warning for older Python stuff (< Python 3.x).
     211    if sys.version_info.major < 3:
     212        print("\nWarning: Running VirtualBox with Python %d.%d is marked as being deprecated.\n" \
     213              "Please upgrade your Python installation to avoid breakage.\n" \
     214              % (sys.version_info.major, sys.version_info.minor))
     215
     216    sVBoxInstallPath = os.environ.get("VBOX_MSI_INSTALL_PATH", None)
     217    if sVBoxInstallPath is None:
     218        sVBoxInstallPath = os.environ.get('VBOX_INSTALL_PATH', None)
     219        if sVBoxInstallPath is None:
     220            print("No VBOX_INSTALL_PATH defined, exiting")
     221            return 1
     222
     223    sVBoxVersion = os.environ.get("VBOX_VERSION", None)
     224    if sVBoxVersion is None:
    75225        # Should we use VBox version for binding module versioning?
    76         vboxVersion = "1.0"
    77 
    78     import platform
     226        sVBoxVersion = "1.0"
     227
     228    if g_fVerbose:
     229        print("VirtualBox installation directory is: %s" % (sVBoxInstallPath))
    79230
    80231    if platform.system() == 'Windows':
    81         cleanupComCache()
     232        cleanupWinComCache()
     233
     234    # Make sure that we always are in the directory where this script resides.
     235    # Otherwise installing packages below won't work.
     236    sCurDir = os.path.dirname(os.path.abspath(__file__))
     237    if g_fVerbose:
     238        print("Current directory is: %s" % (sCurDir))
     239    try:
     240        os.chdir(sCurDir)
     241    except OSError as exc:
     242        print("Changing to current directory failed: %s" % (exc))
    82243
    83244    # Darwin: Patched before installation. Modifying bundle is not allowed, breaks signing and upsets gatekeeper.
    84245    if platform.system() != 'Darwin':
    85         vboxSdkDest = os.path.join(vboxDest, "sdk")
    86         patchWith(os.path.join(os.path.dirname(sys.argv[0]), 'vboxapi', '__init__.py'), vboxDest, vboxSdkDest)
    87 
    88     setup(name='vboxapi',
    89           version=vboxVersion,
    90           description='Python interface to VirtualBox',
    91           author='Oracle Corp.',
    92           author_email='[email protected]',
    93           url='https://www.virtualbox.org',
    94           packages=['vboxapi']
    95           )
     246        # @todo r=andy This *will* break the script if VirtualBox installation files will be moved.
     247        #              Better would be patching the *installed* module instead of the original module.
     248        sVBoxSdkPath = os.path.join(sVBoxInstallPath, "sdk")
     249        fRc = patchWith(os.path.join(sCurDir, 'src', 'vboxapi', '__init__.py'), \
     250                        sVBoxInstallPath, sVBoxSdkPath)
     251        if not fRc:
     252            return 1
     253
     254    try:
     255        #
     256        # Detect which installation method is being used.
     257        #
     258        # This is a bit messy due the fact that we want to support a broad range of older and newer
     259        # Python versions, along with distributions which maintain their own Python packages (e.g. newer Ubuntus).
     260        #
     261        fInvokeSetupTools = False
     262        if sys.version_info >= (3, 12): # Since Python 3.12 there are no distutils anymore. See PEP632.
     263            try:
     264                from setuptools import setup
     265            except ImportError:
     266                print("ERROR: setuptools package not installed, can't continue. Exiting.")
     267                return 1
     268            setup(cmdclass={"install": setupInstallClass})
     269        else:
     270            if sys.version_info >= (3, 3): # Starting with Python 3.3 we use pyproject.toml by using setup().
     271                try:
     272                    from distutils.core import setup # pylint: disable=deprecated-module
     273                    setup(cmdclass={"install": setupInstallClass})
     274                except ImportError:
     275                    print("distutils[.core] package not installed/available, falling back to legacy setuptools ...")
     276                    fInvokeSetupTools = True # Invoke setuptools as a last resort.
     277            else: # Python 2.7.x + Python < 3.6 legacy cruft.
     278                fInvokeSetupTools = True
     279
     280            if fInvokeSetupTools:
     281                if g_fVerbose:
     282                    print("Invoking setuptools directly ...")
     283                setupTool = setup(name='vboxapi',
     284                            version=sVBoxVersion,
     285                            description='Python interface to VirtualBox',
     286                            author='Oracle Corp.',
     287                            author_email='[email protected]',
     288                            url='https://www.virtualbox.org',
     289                            package_dir={'': 'src'},
     290                            packages=['vboxapi'])
     291                if setupTool:
     292                    sPathInstalled = setupTool.command_obj['install'].install_lib
     293                    if sPathInstalled not in sys.path:
     294                        print("\nWARNING: Installation path is not in current module search path!")
     295                        print("         This might happen on OSes / distributions which only maintain packages by")
     296                        print("         a vendor-specific method.")
     297                        print("Hints:")
     298                        print("- Check how the distribution handles user-installable Python packages.")
     299                        print("- Using setuptools directly might be deprecated on the distribution.")
     300                        print("- Using \"pip install ./vboxapi\" within a virtual environment (virtualenv)")
     301                        print("  might fix this.\n")
     302                        sys.path.append(sPathInstalled)
     303
     304                print("Installed to: %s" % (sPathInstalled))
     305
     306    except RuntimeError as exc:
     307        print("ERROR: Installation of VirtualBox Python bindings failed: %s" % (exc))
     308        return 1
     309
     310    if  fInvokeSetupTools \
     311    and not testVBoxAPI():
     312        return 1
     313    return 0
    96314
    97315if __name__ == '__main__':
    98     main(sys.argv)
     316    sys.exit(main())
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette