views:

190

answers:

4

I'm developing an open source application called GarlicSim.

Up to now I've been developing it only for Python 2.6. It seems not to work on any other version.

I decided it's important to produce versions of it that will support other versions of Python. I'm thinking I'll make a version for 2.5, 3.1 and maybe 2.4.

So I have several questions:

  1. What would be a good way to organize the folder structure of my repo to include these different versions?
  2. What would be a good way to 'merge' changes I do in one version of the code to other versions? I know how to do merges in my SCM (which is git), but these are folders that are all in the same repo, and I want to do a merge between them. There is of course the option of having a repo for each version, but I think it's not a good idea.

Does anyone have any suggestions?

+1  A: 

I would try to maintain one branch to cover all the python 2.4-2.6

The differences are not so great, after all if you have to write a bunch of extra code for 2.4 to do something that is easy in 2.6, it will be less work for you in the long run to use the 2.4 version for 2.5 and 2.6.

Python 3 should have a different branch, you should still try to keep as much code in common as you can.

gnibbler
That would mean I won't be able to use idioms from Python 2.6, which would suck. I love using context managers, for example.
cool-RR
my point was that you write the version with the context manager. then you rewrite it for 2.4. the 2.4 version will still work in 2.6 so you have duplicated some effort. There is nothing stopping you from maintaining 4 branches but you will have lots of merging to do. Perhaps have a "common" directory for code that is same across all. I don't think there are any magic pixies to do all the merging for you though
gnibbler
I'm okay with doing all the merging, as long as I can work with the context manager on my 2.6 copy.
cool-RR
from `__future__` import with_statement works for 2.5, so perhaps drop 2.4 support if you use context managers
gnibbler
Good idea, I might do that.
cool-RR
+1  A: 

If your code is not overly dependent on the run-time performance in exception handlers, you might even get away without having a separate branch for Py3. I've managed to keep one version of pyparsing for all of my Py2.x versions, although I've had to stick with a "lowest common denominator" approach, meaning that I have to forego using some constructs like generator expressions, and to your point, context managers. I use dicts in place of sets, and all my generator expressions get wrapped as list comprehensions, so they will still work going back to Python 2.3. I have a block at the top of my code that takes care of a number of 2vs3 issues (contributed by pyparsing user Robert A Clark):

_PY3K = sys.version_info[0] > 2
if _PY3K:
    _MAX_INT = sys.maxsize
    basestring = str
    unichr = chr
    unicode = str
    _str2dict = set
    alphas = string.ascii_lowercase + string.ascii_uppercase
else:
    _MAX_INT = sys.maxint
    range = xrange
    def _str2dict(strg):
        return dict( [(c,0) for c in strg] )
    alphas = string.lowercase + string.uppercase

The biggest difficulty I've had has been with the incompatible syntax for catching exceptions, that was introduced in Py3, changing from

except exceptiontype,varname:

to

except exceptionType as varname:

Of course, if you don't really need the exception variable, you can just write:

except exceptionType:

and this will work on Py2 or Py3. But if you need to access the exception, you can still come up with a cross-version compatible syntax as:

except exceptionType:
    exceptionvar = sys.exc_info()[1]

This has a minor run-time penalty, which makes this unusable in some places in pyparsing, so I still have to maintain separate Py2 and Py3 versions. For source merging, I use the utility WinMerge, which I find very good for keeping directories of source code in synch.

So even though I keep two versions of my code, some of these unification techniques help me to keep the differences down to the absolute incompatible minimum.

Paul McGuire
Helpful information, Paul. I think I'll stick with having a separate branch for v3 though -- I wouldn't want this cruft in my code.
cool-RR
Perhaps it is possible to generate the 3 code using 2to3 or in the case of the exceptions for instance, you could just use a regex replacement to build the 3 tree from the 2 tree.
gnibbler
@gnibbler: It's easier to use 2to3 than regexp. 2to3 already has the code to do the transform, no need to reinvent that wheel.
Lennart Regebro
In other words for code re-usability you would stick with this answer. For performance you would make separate the code in two branches.
Diones
@Diones: Performance differences here are negligible, and can safely be completely ignored.
Lennart Regebro
@Lennart: Unfortunately, pyparsing uses the stack for testing paths within the parser grammar, with exceptions acting to unwind the stack when a grammar path fails to match. I have converted the exception handling in non-critical performance areas of pyparsing, but there are 1 or 2 places where I *have* to leave it in the "except ParseException,pe:" format - I've measured the performance penalty of changing to using sys.exc_info() at about 10%, which is not something I want to impose on Py2 users just to be compatible with Py3. That is the main reason I'm keeping 2 codebases.
Paul McGuire
@Paul: Why don't you use 2to3?
Lennart Regebro
@Lennart: I started working on this conversion before 2to3 was written. In fact, I had a single source compatible with both Py2 and early alpha releases of Py3, until a late development in Py3 added the incompatible form of the except statement. So by the time 2to3 was available, I already had my py3-converted version finished. But you make a good point, and it would be better for me to run 2to3 to generate pyparsing_py3.py, instead of manually keeping the two in synch. What version of Python does 2to3 require? I am still at 2.5.2 for my main development environment.
Paul McGuire
+3  A: 

You need separate branches for separate versions only in the rarest of cases. You mention context managers, and they are great, and it would suck not to use them, and you are right. But for Python 2.4 you will have to not use them. So that will suck. So therefore, if you want to support Python 2.4 you'll have to write a version without context managers. But that one will work under Python 2.6 too, so there is no point in having separate versions there.

As for Python 3, having a separate branch there is a solution, but generally not the best one. For Python 3 support there is something called 2to3 which will convert your Python 2 code to Python 3 code. It's not perfect, so quite often you will have to modify the Python 2 code to generate nice Python 3 code, but the Python 2 code has a tendency to become better as a result anyway.

With Distribute (a maintained fork of setuptools) you can make this conversation automatically during install. That way you don't have to have a separate branch even for Python 3. See http://bitbucket.org/tarek/distribute/src/tip/docs/python3.txt for the docs on that.

As Paul McGuire writes it is even possible to support Python 3 and Python 2 with the same code without using 2to3, but I would not recommend it if you want to support anything else than 2.6 and 3.x. You get too much of this ugly special hacks. With 2.6 there is enough forwards compatibility with Python 3 to make it possible to write decent looking code and support both Python 2.6 and 3.x, but not Python 2.5 and 3.x.

Lennart Regebro
A: 

I eventually decided to have 4 different forks for my project, for 2.4, 2.5, 2.6 and 3.1. My main priority is 2.6 and I don't want to compromise the elegance of that code for the sake of 2.4. So the ugly compatibility hacks will be on the lower versions, not the higher versions.

cool-RR