takluyver / pynsist

Build Windows installers for Python applications
https://pynsist.readthedocs.io/
Other
882 stars 119 forks source link

Use module version for version field #127

Open Alexis-B opened 6 years ago

Alexis-B commented 6 years ago

Hi,

I'm trying to configure pynsist (great work by the way) to init the version field using the application module version. In the doc and all exemples, I always see "version=1.0" . Is there a way tu put "version=mymodule.version", of something like that? If not, this would be a great feature.

Alexis

takluyver commented 6 years ago

You can't currently do this using a config file, which is the normal way I recommend you use Pynsist. I'll consider it as a possible feature for future versions.

You can do it through the Pynsist Python API, with code something like this:

import mymodule
from nsist import InstallerBuilder

InstallerBuilder('myapp',  mymodule.__version__,
                 ... # Other parameters go here
                ).run()

I consider this API semi-public: it's liable to break more often, and with less helpful error messages, than running Pynsist on a config file.

Alexis-B commented 6 years ago

Thanks! How would you provide the 'entry_point' parameter using this method? I'm getting various errors with this:

builder = InstallerBuilder( src.__name__, src.__version__, {'Application':{'entry_point':'src.main:main'}}, packages=['PyQt5','sip','matplotlib'], build_dir='packaging' )

The project is not very complex, and the configuration will not change much. So I'm thinking that if it works once, it should be OK...

takluyver commented 6 years ago

I think you need to ensure that that shortcut dictionary contains 'icon' and 'console' as well - they're optional in the config file but not in that API.

Alexis-B commented 6 years ago

Ok, thanks. I finally gave up: I found that I had to provide twice the icon (first in "shortcuts" and then in "icon"), but mainly while I though I gave all informations, the installer create a windows shortcut "Application", instead of the name of the application. Initially my goal was to update the version number from the module, so I'll do it manually (and possibly upgrade if the functionnality is integrated in pynsist). By the way thanks for your help

takluyver commented 6 years ago

No problem. You might want to look at bumpversion as a way to streamline updating the version number in several places.

cachitas commented 6 years ago

I though I could jump in and leave here, as an example, my use of the builder API with a PyQt5 application. Basically, it looks like this:

import nsist
from pathlib import Path

from src import application

icon = Path('icons', application.ICON).resolve()
requirements = open('requirements.txt').read().strip().split('\n')

builder = nsist.InstallerBuilder(
    appname=application.NAME,
    version=application.VERSION,
    icon=icon,
    shortcuts={
        application.NAME: {
            'entry_point': 'src.application:main',
            'console': False,
            'icon': icon,
        }
    },
    py_version='3.6.0',
    py_bitness=64,
    pypi_wheel_reqs=requirements,
)

builder.run()

I hope this minimal setup can help you. Of course I am open to suggestions to improve it. By no means I claim this is the way to go. I use this in a build.py script where I also prepare the *.ui and resources.qrc files, required by PyQt5.

Alexis-B commented 6 years ago

Interesting, thanks for this contribution! I think I'll try this at some point and let you know if all is good

albertogomcas commented 6 years ago

Alternatively, you can use a template for the cfg and fill it at build time. (This is a very, very rough example which covers just sunny day builds, but a bit of cleanup and combining this with bumpversion can get you going a really long way)

Assumptions are you have a template.cfg with version = $VERSION and inside your package your have a version.py with __version__="major.minor.patch" (the usual)

import os
import glob
import subprocess

PACKAGE = "mypackage"

if __name__ == "__main__":
    exec(open(os.path.join(PACKAGE, "version.py")).read())  # loads __version__ in this scope
    build_config_temp = f"build_{__version__}.cfg"

    with open("build_template.cfg", "rt") as btfile:
        template = btfile.read()
        filled_template = template.replace("$VERSION", __version__)
        with open(build_config_temp, "wt+") as bvfile:
            bvfile.write(filled_template)

    #clean up previous build if exists
    old_builds = glob.glob(f"build/nsis/*{__version__}.exe")
    for ob in old_builds:
        print(f"Deleting {ob}")
        os.remove(ob)

    subprocess.call(f"pynsist {build_config_temp}")
    os.remove(build_config_temp)
takluyver commented 6 years ago

If you do use bumpversion (which I'd recommend), you shouldn't need to write a templating layer - just add installer.cfg to the files for bumpversion to update.

The templating approach should work fine too. Amusingly, Pynsist itself uses templating to generate the NSIS file, and NSIS has its own preprocessor system. It's templates all the way down. ;-)