Skip to content

Precise versioning with local branches #118

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: next_release
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ venv
build*/
dist*/
autodoc*
switch_model/data/installed_version.txt
126 changes: 126 additions & 0 deletions get_and_record_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from __future__ import print_function
import argparse
import logging
import os
import subprocess

"""
Define a precise package version that includes any git digests for any commits
made subsequently to a package release. The base version (2.0.4 in this
example) is obtained from the last tag that starts with "2". version. Also use
the git-standard "dirty" suffix instead of "localmod" for installations from
code that hasn't been committed.

Example:
1) 112 commits were made subsequent to an official release (possibly on
a branch), plus some uncommitted modifications. The version would be:
2.0.4+112+{gitsha}+dirty
2) Same scenario, but no uncommitted modifications: 2.0.4+112+{gitsha}
3) No commits since the last tagged release: 2.0.4

These functions are encoded into a separate file from setup.py to support
including precise versions in docker tags.
"""

def get_git_version():
"""
Try to get git version like '{tag}+{gitsha}', with the added suffix
"+dirty" if the git repo has had any uncommitted modifications.
The "+{gitsha}" suffix will be dropped if this is the tagged version.
Code adapted from setuptools_git_version which has an MIT license.
https://pypi.org/project/setuptools-git-version/
Note: Only look for tags that start with "2." to avoid tags of
non-released versions.
"""
git_command = "git describe --all --long --match '2.*' --dirty --always"
fmt = '{base_v}+{count}+{gitsha}{dirty}'

git_version = subprocess.check_output(git_command, shell=True).decode('utf-8').strip()
# The prefix tags/ may not appear in every context, and should be ignored.
match = re.match("(tags/)?(.*)-([\d]+)-g([0-9a-f]+)(-dirty)?", git_version)
assert match, (
"Trouble parsing git version output. Got {}, expected 3 or 4 things "
"separated by dashes. This has been encountered when the local git repo "
"lacks tags, which can be solved by fetching from the main repo:\n"
"`git remote add main https://github.com/switch-model/switch.git && "
"git fetch --all`".format(git_version)
)
parts = match.groups()[1:]
if parts[-1] == '-dirty':
dirty = '+dirty'
else:
dirty = ''
base_v, count, sha = parts[:3]
if count == '0' and not dirty:
return base_v
return fmt.format(base_v=base_v, count=count, gitsha=sha, dirty=dirty)

def get_and_record_version(repo_path):
"""
Attempt to get an absolute version number that includes commits made since
the last release. If that succeeds, record the absolute version and use it
for the pip catalog. If that fails, fall back to something reasonable and
vague for the pip catalog, using the data from base_version.py.
"""
pkg_dir = os.path.join(repo_path , 'switch_model' )
data_dir = os.path.join(pkg_dir, 'data' )
__version__ = None
try:
__version__ = get_git_version()
with open(os.path.join(data_dir, 'installed_version.txt'), 'w+') as f:
f.write(__version__)
except subprocess.CalledProcessError as e:
logging.warning(
"Could not call git as a subprocess to determine precise version."
"Falling back to using the static version from version.py")
logging.exception(e)
except AssertionError as e:
logging.warning("Trouble parsing git output.")
logging.exception(e)
except Exception as e:
logging.warning(
"Trouble getting precise version from git repository; "
"using base version from switch_model/version.py. "
"Error was: {}".format(e)
)
if __version__ is None:
module_dat = {}
with open(os.path.join(pkg_dir, 'version.py')) as fp:
exec(fp.read(), module_dat)
__version__ = module_dat['__version__']
return __version__

def get_args():
parser = argparse.ArgumentParser(
description='Get a precise local version of this git repository',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
'--verbose', '-v', dest='verbose', default=False,
action='store_const', const=logging.WARNING,
help='Show information about model preparation and solution')
parser.add_argument(
'--very-verbose', '-vv', dest='verbose', default=False,
action='store_const', const=logging.INFO,
help='Show more information about model preparation and solution')
parser.add_argument(
'--very-very-verbose', '-vvv', dest='verbose', default=False,
action='store_const', const=logging.DEBUG,
help='Show debugging-level information about model preparation and solution')
parser.add_argument(
'--quiet', '-q', dest='verbose', action='store_false',
help="Don't show information about model preparation and solution "
"(cancels --verbose setting)")

args = parser.parse_args()
return args

def main():
args = get_args()
if args.verbose:
logging.basicConfig(format='%(levelname)s:%(message)s', level=args.verbose)
repo_path = os.path.dirname(os.path.realpath(__file__))
__version__ = get_and_record_version(repo_path)
print(__version__)

if __name__ == "__main__":
main()
14 changes: 8 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@
import os
from setuptools import setup, find_packages

# Get the version number. Strategy #3 from https://packaging.python.org/single_source_version/
version_path = os.path.join(os.path.dirname(__file__), 'switch_model', 'version.py')
version = {}
with open(version_path) as f:
exec(f.read(), version)
__version__ = version['__version__']
from get_and_record_version import get_and_record_version

repo_path = os.path.dirname(os.path.realpath(__file__))
__version__ = get_and_record_version(repo_path)

def read(*rnames):
return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
Expand Down Expand Up @@ -55,6 +53,9 @@ def read(*rnames):
'Topic :: Software Development :: Libraries :: Python Modules'
],
packages=find_packages(include=['switch_model', 'switch_model.*']),
package_data = {
'switch_model': ['data/*']
},
keywords=[
'renewable', 'power', 'energy', 'electricity',
'production cost', 'capacity expansion',
Expand All @@ -65,6 +66,7 @@ def read(*rnames):
'pint', # needed by Pyomo when we run our tests, but not included
'testfixtures', # used for standard tests
'pandas', # used for input upgrades and testing that functionality
'setuptools', # For parsing version numbers; it is part of almost all python distributions, but not guaranteed.
],
extras_require={
# packages used for advanced demand response, progressive hedging
Expand Down
3 changes: 3 additions & 0 deletions switch_model/data/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""This directory contains any necessary package data or default configuration
files.
"""
8 changes: 8 additions & 0 deletions switch_model/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from pyomo.environ import *
import pyomo.opt

import switch_model

# Define string_types (same as six.string_types). This is useful for
# distinguishing between strings and other iterables.
try:
Expand Down Expand Up @@ -263,6 +265,12 @@ def post_solve(instance, outputs_dir=None):
if hasattr(module, 'post_solve'):
module.post_solve(instance, outputs_dir)

# Save the precise version used to solve this problem.
version_path = os.path.join(outputs_dir, 'software_version.txt')
with open(version_path, 'w') as f:
f.write("This problem was solved with switch version {}.{}".format(
switch_model.__version__, os.linesep))


def min_data_check(model, *mandatory_model_components):
"""
Expand Down
11 changes: 10 additions & 1 deletion switch_model/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,13 @@
distribution because it needs to be executed before Switch (and its
dependencies) are installed.
"""
__version__='2.0.6-dev'
import os

base_version = '2.0.6-dev'

try:
DATA_ROOT = os.path.join(os.path.dirname(__file__), 'data')
with open(os.path.join(DATA_ROOT, 'installed_version.txt'), 'r') as f:
__version__ = f.read().strip()
except (IOError, NameError):
__version__ = base_version