On 20/06/2013 3:16 AM , Mark Hammond wrote:

> I've wanted bdist_msi to work for ages.  There was some issue with the 
> install scripts that always caused grief, and my memory is hazy - it *might* 
> be that the uninstall process doesn't run the post-install script at the 
> correct time - ie, that it runs *after* the uninstall, which means pywin32 
> isn't available to undo some of the stuff it did as it can't import pywin32 
> itself.  

There's a comment in the pywin32_postinstall.py script that says:
        elif arg == "-remove":
            # bdist_msi calls us before uninstall, so we can undo what we
            # previously did.  Sadly, bdist_wininst calls us *after*, so
            # we can't do much at all.
            if not is_bdist_wininst:
                uninstall()

And I've confirmed that this comment re: bdist_msi is correct.  Given this, I 
expect that these issues were with the bdist_wininst, and so a working msi 
installer might be even better than the existing installer on that score.

> Another problem I'd expect to find is that the post-install script tries to 
> tell 
> distutils about new files and registry entries created at install time - this 
> probably 
> fails silently now (ie, it might *appear* to work but probably leaves 
> trash behind.)

I couldn't identify the code that does this thing you mention above, but given 
this concern, I did some careful checking.  As far as I can tell, there are 3 
complaints re: trash:

1) What's left behind in the file system after an install followed by an 
immediate uninstall are 3 site-packages subtrees: win32\lib, 
win32com\{client,server,servers}, and win32comext\{axscript,shell}, and all of 
these only contain .pyc files.  Unfortunately, as .pycs are generated 
post-install by the interpreter, and I'm not sure how to identify the ones 
associated with just the pywin32 stuff, so I don't quite know how to get rid of 
them so that these directories are cleanly removed.  Further, if the other 
directories installed get invoked, I'm sure that there will be .pycs generated 
there too.  Is there a way to have the postinstall script know (without 
hardcoding), which directories it should recursively clean out the .pycs from?  
Or is there some other mechanism I can use that I'm not aware of?

2) There is also much black magic juju-bwana re: create_shortcut that is in 
that script and I don't entirely understand...  But running the postinstall.py 
-install manually on windows 7, it says:

   Can't install shortcuts - 
u'C:\\Users\\DevTest7\\AppData\\Roaming\\Microsoft\\Windows\\Start 
Menu\\Programs\\Python 2.7' is not a folder.

I don't yet know why it's trying to use this folder, but this whole thing is 
part of the black magic I don't understand, and there are comments in the 
postinstall script that suggest that none of this stuff actually works on 
windows 7 anyway.  Do we know if this step actually works for win7 with the 
current installer?

3) And on manually trying the -remove version of the script, it complained:
  Failed to unregister Pythonwin: [Error 5] Access is denied

But I tracked this down to a bug in the uninstall code in the postinstall.py 
script; _winreg.DeleteKey() doesn't allow you delete a key with subkeys, but 
you had created two keys with (command) subkeys in the install process.  Once I 
fixed the postinstall script to Delete the subkeys and then the root key, the 
script properly removed the pythonwin.exe keys and unregistered it.  (I suspect 
this is actually currently broken in the regular installer as well if you use 
the bdist_wininst distutils mechanism, unless that code is circumvented in your 
.exe installer.)  (I will supply a patch for this too.)

Should I be looking for anything else while I'm at it?

> > 1) I couldn't build from the latest source version (218).  [...]
> Yeah, I screwed that release up in that regard.  I normally unzip the source 
> package and attempt to build 
> it before release, but forgot that time :(

Ok, well, with luck, the changes to allow construction of an official msi can 
become part of version 219.  ;)

> I have used the patch version in the past - mainly when I've found one single 
> package was uploaded 
> incorrectly due to a build issue rather than due to a bug in the code itself.
> eg: https://sourceforge.net/projects/pywin32/files/pywin32/Build216/ - where 
> is a "216.1" build for Python 3.2.
>
> The other thing I've done in the past is to upload custom pywin32 builds for 
> people when they've reported a 
> bug but can't rebuild the world to test it - I usually change that version 
> string immediately after release, so any 
> such "interim" builds can't be confused with the official builds (but such 
> builds don't end up on sourceforge)
>
> But I'm happy to change things in this area if it blocks support for MSI.  
> The other thing I'd want to make sure is 
> that the Python version in that number does something sane - eg, I wouldn't 
> want the MSI infrastructure to 
> think that "2.7.217" was an earlier version (or even related in any way) than 
> "3.2.216" etc.

Yeeeaaaahhh, the docstring on the strict version class says "the rationale for 
this version string numbering system will be explained in the distutils 
documentation," but no such rationale is provided there.  Further, here's a 
snippet of the bdist_msi.py code:

        version = metadata.get_version()
        # ProductVersion must be strictly numeric
        # XXX need to deal with prerelease versions
        sversion = "%d.%d.%d" % StrictVersion(version).version
        # Prefix ProductName with Python x.y, so that
        # it sorts together with the other Python packages
        # in Add-Remove-Programs (APR)
        fullname = self.distribution.get_fullname()
        if self.target_version:
            product_name = "Python %s %s" % (self.target_version, fullname)
        else:
            product_name = "Python %s" % (fullname)

This assumes that the StrictVersion(version).version is literally a tuple of 3 
components; otherwise the string format would generate an exception; 
fortunately, if you don't provide the 3rd component, it appends a 0 for you 
(see below).   Also, the above code automatically sticks "Python x.y" into the 
product name used, so I don't think we need to include those in the version 
string like I was.

>From the distutils\version.py file:
class StrictVersion (Version):
[...]
    version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$',
                            re.VERBOSE)

    def parse (self, vstring):
        match = self.version_re.match(vstring)
        if not match:
            raise ValueError, "invalid version number '%s'" % vstring

        (major, minor, patch, prerelease, prerelease_num) = \
            match.group(1, 2, 4, 5, 6)

        if patch:
            self.version = tuple(map(string.atoi, [major, minor, patch]))
        else:
            self.version = tuple(map(string.atoi, [major, minor]) + [0])

        if prerelease:
            self.prerelease = (prerelease[0], string.atoi(prerelease_num))
        else:
            self.prerelease = None

Given the code in bdist_msi using the current python release major.minor 
release number already in the product name, this suggests that we should just 
specify build_id as major, and your patch number (usually .0) as minor in the 
version string.  That way, you don't have to change a thing with respect to 
versioning, and instead of my previous patch, we can just use your 
build_id_patch variable as the dist version, eg:

  dist = setup(name="pywin32",
!       version=str(build_id),
        description="Python for Window Extensions",
        long_description="Python extensions for Microsoft Windows\n"
                         "Provides access to much of the Win32 API, the\n"
                         "ability to create and use COM objects, and the\n"
                         "Pythonwin environment.",
--- 2328,2340 ----
               'build_py' : my_build_py,
               'build_scripts' : my_build_scripts,
             }

  dist = setup(name="pywin32",
!       # msi construction needs a major.minor version, so we use build_id_patch
!       # for this instead:
!       version=str(build_id_patch),
        description="Python for Window Extensions",
        long_description="Python extensions for Microsoft Windows\n"

and that's the only change required in the setup file!

This produces an msi named pywin32-217.0.win32-py2.7.msi, which also (mostly) 
avoids the repetitive redundancy there too.

> > It turns out that this distutils bug was reported back in August of 
> > 2012, and a patch has even been supplied (see
> > http://bugs.python.org/issue15797.)  When I applied the supplied 
> > patch, and rebuilt my msi, it properly ran the postinstall script, and 
> > (as far as I can tell) the resulting install works perfectly!
>
> I can probably help push that through.  Although it seems unlikely for it to 
> be accepted into 
> the 2.x branch, which is a problem.  OTOH though, if you help get this 
> working and you only 
> care about 3.x, I could certainly live with the fact that MSI installers are 
> only available for 3.x...

Regrettably, I need them for 2.7. 

> I wonder if there are heuristics the post-install script could use to work 
> around this itself?  
> You said the uninstall worked - did it actually leave a clean file-system 
> afterwards?  If so, it 
> would seem the issue I mentioned before might not be a problem at all - but 
> it's not clear 
> to me how, even if pywin32 was fully unavailable at uninstall time, how we 
> could determine 
> if this invocation is for a re-install or an uninstall (ie, it's not clear to 
> me that the absence of 
> certain files would be a reliable guide here)

Yeah, I thought about that, and struggled with the same issues:  without 
arguments, how would it know whether it was being invoked for install or 
remove?  But when I discovered what the real problem was, and went a-googling, 
I discovered that bug report and patch submission against the distutils, and so 
I thought that was the better choice.  If I can resolve the other issues, and 
submit patches, is there any way I can convince you to to apply that distutils 
patch on your system, so that you can then use it to build the msi's?  If they 
work and come from you, I suspect no one will care that they were built with an 
"unsanctioned" version of distutils...

Regards,
/Will
_______________________________________________
python-win32 mailing list
python-win32@python.org
http://mail.python.org/mailman/listinfo/python-win32

Reply via email to