diff --git a/make_release.py b/make_release.py index 3b5c71f8..579ade54 100644 --- a/make_release.py +++ b/make_release.py @@ -1,4 +1,4 @@ -'''Eventaully, this file will execute the necessary steps. +'''Eventaully, this file will execute the necessary steps. For now, it just prints what they are.''' import os @@ -10,6 +10,7 @@ import colorama import semantic_version +from semantic_version import Version from colorama import Fore, Style from isort import SortImports from minchin import text @@ -17,11 +18,16 @@ # also requires: # - green +# - pip +# - setuptools +# - wheel # # assumes: # - git on commandline # - tests written for green # - green available on command line +# - a valid `.pypirc` file with credentials for TestPyPI and PyPI +# - your project is already registered on TestPyPi and PyPI __version__ = "0.1.0" @@ -34,14 +40,26 @@ def here_directory(): return(Path.cwd()) +def module_name(): + return("colourettu") + + def source_directory(): - return(here_directory() / "colourettu") + return(here_directory() / module_name()) def test_directory(): return(here_directory() / 'tests') +def doc_directory(): + return(here_directory() / 'docs') + + +def dist_directory(): + return(here_directory() / 'dist') + + def version_file(): return(source_directory() / "__init__.py") @@ -50,6 +68,34 @@ def changelog_file(): return(here_directory() / 'docs' / "changelog.rst") +def other_dependancies(server, environment): + """Installs things that need to be in place before installing the main package""" + server = server.lower() + # Pillow is not on TestPyPI + if server is "local": + pass + elif server in ["testpypi", "pypitest"]: + print(" **Install Pillow**") + subprocess.call([environment + '\\Scripts\\pip.exe', 'install', 'Pillow'], shell=True) + elif server is "pypi": + pass + else: + print(" **Nothing more to install**") + + +def server_url(server_name): + server_name = server_name.lower() + if server_name in ["testpypi", "pypitest"]: + return(r"https://testpypi.python.org/pypi") + elif server_name in ["pypi", ]: + return(r"https://pypi.python.org/pypi") + else: + # we really should throw an error here... + return() + +# //-------------------------------------------------------------------------// + + def git_check(): """Check for uncomitted changes""" git_status = subprocess.check_output(['git', 'status', '--porcelain']) @@ -70,15 +116,15 @@ def sort_imports(my_dir): def run_tests(): """Run tests""" - text.clock_on_right('Run tests') test_status = subprocess.check_call(['green', str(test_directory()), '-vv']) if test_status is not 0: exit(Fore.RED + 'Please make all tests pass to continue') def update_version_number(update_level='patch'): - """Update version number""" - text.clock_on_right('Update version number') + """Update version number + + Returns a semantic_version object""" """Find current version""" temp_file = version_file().parent / ("~" + version_file().name) @@ -89,10 +135,10 @@ def update_version_number(update_level='patch'): if version_matches: bare_version_str = version_matches.groups(0)[0] if semantic_version.validate(bare_version_str): - current_version = semantic_version.Version(bare_version_str) + current_version = Version(bare_version_str) print("{}Current version is {}".format(" "*4, current_version)) else: - current_version = semantic_version.Version.coerce(bare_version_str) + current_version = Version.coerce(bare_version_str) if not text.query_yes_quit("{}I think the version is {}. Use it?".format(" "*4, current_version), default="yes"): exit(Fore.RED + 'Please set an initial version number to continue') @@ -122,7 +168,9 @@ def update_version_number(update_level='patch'): def add_release_to_changelog(version): - """Add release line at the top of the first list it finds""" + """Add release line at the top of the first list it finds + + Assumes your changelog in managed with `releases`""" temp_file = changelog_file().parent / ("~" + changelog_file().name) now = datetime.today() release_added = False @@ -147,29 +195,58 @@ def add_release_to_changelog(version): os.remove(str(temp_file)) +def run_sphinx(): + """Runs Sphinx via it's `make html` command""" + old_dir = here_directory() + os.chdir(str(doc_directory())) + doc_status = subprocess.check_call(['make', 'html'], shell=True) + os.chdir(str(old_dir)) # go back to former working directory + if doc_status is not 0: + exit(Fore.RED + 'Something broke generating your documentation...') + + +def build_distribution(): + build_status = subprocess.check_call(['python', 'setup.py', 'sdist', 'bdist_egg', 'bdist_wheel']) + if build_status is not 0: + exit(Fore.RED + 'Something broke tyring to package your code...') + + +def check_local_install(version, ext, server="local"): + all_files = list(dist_directory().glob('*.{}'.format(ext))) + the_file = all_files[0] + for f in all_files[1:]: + if f.stat().st_mtime > the_file.stat().st_mtime: + the_file = f + + environment = 'env-{}-{}-{}'.format(version, ext, server) + if server == "local": + pass + else: + # upload to server + print(" **Uploading**") + subprocess.call(['twine', 'upload', str(the_file), '-r', server]) + + if (here_directory() / environment).exists(): + shutil.rmtree(environment) # remove directory if it exists + subprocess.call(['virtualenv', environment]) + if server == "local": + subprocess.call([environment + '\\Scripts\\pip.exe', 'install', str(the_file)], shell=True) + else: + other_dependancies(server, environment) + print(" **Install from server**") + subprocess.call([environment + '\\Scripts\\pip.exe', 'install', '-i', server_url(server), module_name() + "==" + str(version)], shell=True) + print(" **Test version of install package**") + test_version = subprocess.check_output([environment + '\\Scripts\\python.exe', '-c', "exec(\"\"\"import {0}\\nprint({0}.__version__)\\n\"\"\")".format(module_name())], shell=True) + test_version = test_version.decode('ascii').strip() + # print(test_version, type(test_version), type(expected_version)) + if (Version(test_version) == version): + print('{}{} install {} works!{}'.format(Fore.GREEN, server, ext, Style.RESET_ALL)) + else: + exit('{}{} install {} broken{}'.format(Fore.RED, server, ext, Style.RESET_ALL)) + def print_all_steps(): - print('Add release to documenation') - print('Update documenation') - print(' cd docs') - print(' make html') - print(' index.html') - print(' cd ..') - print('Build distribution') - print(' python -m pip install pip -U') - print(' pip install setuptools wheel twine -U') - print(' python setup.py sdist bdist_egg bdist_wheel') - print('Test distribution') # see https://hynek.me/articles/sharing-your-labor-of-love-pypi-quick-and-dirty/ - # use `vex` for virtual envs? - print('Push to test PyPI') - print(' python setup.py sdist upload -r pypitest') - print(' pip install -i https://testpypi.python.org/pypi [package-name]') - print(' everything working?') - print('Push to real PyPI') - print(' python setup.py sdist upload -r pypi') - print(' python setup.py bdist_wheel upload -r pypi') - print(' everything working??') print('Tag release') print(' tag v(version number)') print(' push tag') @@ -179,31 +256,73 @@ def print_all_steps(): print(' git add -a') print(' git commit -m "Documentation updated!"') print(' cd ..\colourettu') - print('Push to git repo') + print('Push to git repo, including tags') # swtich to twine https://pypi.python.org/pypi/twine/ # twine upload dist/* +# //-------------------------------------------------------------------------// + def main(): colorama.init() text.title("Minchin 'Make Release' for Python v{}".format(__version__)) + print() + text.subtitle("Configuration") print("base dir -> {}".format(here_directory())) print("source -> .\{}\\".format(source_directory().relative_to(here_directory()))) print("test dir -> .\{}\\".format(test_directory().relative_to(here_directory()))) + print("doc dir -> .\{}\\".format(doc_directory().relative_to(here_directory()))) print("version file -> .\{}".format(version_file().relative_to(here_directory()))) print() + print() + text.subtitle("Git -- Clean directory?") # git_check() - # text.clock_on_right('Sort import statements') + + print() + text.subtitle("Sort Import Statements") # sort_imports(source_directory()) # sort_imports(test_directory()) + + print() + text.subtitle("Run Tests") # run_tests() - new_version = update_version_number('patch') - add_release_to_changelog(new_version) + + print() + text.subtitle("Update Version Number") + # new_version = update_version_number('patch') + new_version = update_version_number('prerelease') + + print() + text.subtitle("Add Release to Changelog") + # add_release_to_changelog(new_version) + + print() + text.subtitle("Build Documentation") + # run_sphinx() + + print() + text.subtitle("Build Distributions") + build_distribution() + + # for server in ["local", "testpypi", "pypi"]: + for server in ["testpypi"]: + for file_format in ["zip", "whl"]: + print() + text.subtitle("Test {} Build {}".format(file_format, server)) + check_local_install(new_version, file_format, server) + # tests will pass if the local version of colourettu can be imported + # source files should be moved to a 'src' directory, but this will require + # the module to be properly installed to run tests, generate docs, etc. + # + # uploads -- you cannot upload a file with the same name. Consider adding + # build information to version number to allow retries updating... + # + # registering -- does not attempt # after everything is done - update_version_number('prerelease') + # update_version_number('prerelease') if __name__ == '__main__': main()