Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly get console_scripts install_scripts directory?

I splitted a complex Python script into a package for easier maintenance and distribution. I created a fresh setup.py (using setupmeta) with a console_scripts entry point and the package structure. So far, so good.

I had some unusual requirements, though:

  • the package is to always be installed in a virtualenvwrapper project,
  • so the script is installed in the ${VIRTUAL_ENV}/bin directory...
  • ...and I must create a symlink targetting the script in the ${VIRTUALENVWRAPPER_PROJECT_PATH}/bin directory. (don't ask... :-)

For that purpose:

  1. I added a locate_project_path() function in the setup.py script,

  2. added the following install_and_symlink_script subclass to setuptools.command.install.install:

    class install_and_symlink_script(install):                           
        """Do normal install, but symlink script to project directory""" 
    
        def run(self):                                                   
            install.run(self)                                            
    
            script_path = os.path.join(self.install_scripts, SCRIPT_NAME)
            project_path = locate_project_path()                         
            symlink_path = os.path.join(project_path, "bin", SCRIPT_NAME)
    
            print("creating %s script symlink" % SCRIPT_NAME)            
    
            if os.path.exists(symlink_path):                             
                print("removing existing symlink %s" % symlink_path)     
                os.unlink(symlink_path)                                  
    
            print("creating symlink from %s to %s" % (                   
                symlink_path, script_path))                              
            os.symlink(script_path, symlink_path)                        
    
  3. and configured setup() this way:

    setup(
        ...
        entry_points={
            "console_scripts": ["%s=myscriptpackage.cli:main" % SCRIPT_NAME],
        },
        cmdclass={
            "install": install_and_symlink_script,
        },
        ...
    )
    

When performing a local python ./setup.py install, the package installation and symlink creation works perfectly.

But when performing a pip install git+ssh://.../myscriptpackage.git, it fails:

...
running install_egg_info
Copying src/myscriptpackage.egg-info to build/bdist.linux-x86_64/wheel/myscriptpackage-0.4.0-py2.7.egg-info
running install_scripts
creating my-script script symlink
creating symlink from /path/to/virtualenvwrapper/project/bin/my-script to build/bdist.linux-x86_64/wheel/myscriptpackage-0.4.0.data/scripts/my-script
error: [Errno 17] File exists
error
Failed building wheel for myscriptpackage
...

Meaning, when installing through pip instead of a python ./setup.py install:

  1. it fails to detect an existing symlink, and unlink it.
  2. the install_and_symlink_script.install_scripts variable point to the script inside the build directory instead of the final scripts installation directory... :-|

So... do you know a way to get the correct scripts installation directory, compatible with both a pip install and a python ./setup.py install ?

(Btw, I'm using python 2.7.13, setuptools 39.1.0, virtualenvwrapper 4.8.2 under Debian 9)

UPDATE 1

I knew the error: [Errno 17] File exists issue was coming from the os.path.exists(symlink_path) call.

I just understood why: if a symlink was created from a previous install, that symlink is broken during the new install. os.path.exists returns False for a broken symlink. OTOH, os.path.lexists returns True if the symlink exists, broken or not...

like image 927
Georges Martin Avatar asked Jun 25 '26 13:06

Georges Martin


1 Answers

I found a way to consistently get the scripts installation directory when installing through a python ./setup.py install or through a pip install, using the wheel.paths.get_install_paths() function.

My setuptools custom install command is now:

...
from wheel.paths import get_install_paths

__title__ = "myscriptpackage"
...

class install_and_symlink_script(install):                            
    """Do normal install, but symlink script to project directory"""  

    def run(self):                                                    
        install.run(self)                                             

        wheel_install_paths = get_install_paths(__title__)            
        script_path = os.path.join(wheel_install_paths['scripts'], SCRIPT_NAME)                  
        # instead of: script_path = os.path.join(self.install_scripts, SCRIPT_NAME)

        project_path = locate_project_path()                          
        symlink_path = os.path.join(project_path, "bin", SCRIPT_NAME) 

        print("creating %s script symlink" % SCRIPT_NAME)             

        if os.path.lexists(symlink_path):                             
            print("removing existing symlink %s" % symlink_path)      
            os.unlink(symlink_path)                                   

        print("creating symlink from %s to %s" % (                    
            symlink_path, script_path))                               
        os.symlink(script_path, symlink_path)                         
like image 186
Georges Martin Avatar answered Jun 28 '26 01:06

Georges Martin