3

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...

2
  • could you just run python setup.py .. on after going to the path where setup.py lies. Commented Jan 21, 2019 at 13:27
  • When running a python ./setup.py install, the script is correctly installed in the ${VIRTUAL_ENV}/bin/ directory and the symlink created in the ${PROJECT_PATH}/bin/ directory. (the setuptools.command.install.install.install_scripts variable is set to ${VIRTUAL_ENV}/bin/ directory). With a pip install, the setuptools.command.install.install.install_scripts variable is set to a directory inside a wheel, in the build directory, not the to ${VIRTUAL_ENV}/bin/ directory. I need a pip install to work correctly, as my package has to be included in a requirements.txt file. Commented Jan 21, 2019 at 14:13

2 Answers 2

2

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)                         
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for this excellent Q&A. Would you mind sharing a sample setup.py that works in Python 3? Thanks.
0

I am suspecting about the admin rights on your machine could be an issues. could you please try running cmd in admin mode then after going to the path where setup.py lies could you just run:

 python setup.py 

Next it it is trying to create a symlink in the folder

build/bdist.linux-x86_64/wheel/myscriptpackage-0.4.0.data/scripts/my-script

I request you to try making symlink on your own in this folder.

If this is not the solution then there is some version mismatch. Just let me know if it helps

1 Comment

The entire application and the packages are installed in an isolated virtualenv, not at system-level, thus I should not use any admin commands.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.