From 7405a59ff2f1986922393c2bec77fced9932b28e Mon Sep 17 00:00:00 2001 From: Stephen L Date: Thu, 15 Oct 2015 15:47:02 +0200 Subject: [PATCH] Add `python setup.py make [alias]` (aka Makefile parsing in setup.py) + Tox check Signed-off-by: Stephen L. --- CONTRIBUTE | 13 ++++-- Makefile | 31 ++++++++++++-- RELEASE | 12 +++--- setup.py | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++ tox.ini | 1 + 5 files changed, 167 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTE b/CONTRIBUTE index 1d8e4496..966a04fa 100644 --- a/CONTRIBUTE +++ b/CONTRIBUTE @@ -5,7 +5,8 @@ This file describes how to contribute changes to the project, and how to upload to the pypi repository. Most of the management commands have been directly placed inside -the Makefile, so you just have to run `make [alias]`. +the Makefile, so you just have to run `python setup.py make [alias]`, which +is equivalent to using `make [alias]` but without requiring `make`. Note: to use the Makefile on Windows, you need to install make.exe, for example by installing [MinGW MSYS](http://www.mingw.org/wiki/msys). @@ -46,6 +47,12 @@ The standard way to run the tests: - `cd` to the root of the `tqdm` directory (in the same folder as this file) - run the following command: +``` +python setup.py make test +``` + +or + ``` make test ``` @@ -78,10 +85,10 @@ Alternatively, you can use `nose` to run the tests just for your Python version: - run the following commands: ``` -make alltests +python setup.py make alltests ``` -Or if you don't have `make`: +Or if it doesn't work, you can do: ``` nosetests --with-coverage --cover-package=tqdm -v tqdm/ diff --git a/Makefile b/Makefile index ee9ea17f..f1f85fdd 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,28 @@ -.PHONY: all flake8 test coverage +# IMPORTANT: to be compatible with `python setup.py make alias`, you must make +# sure that you only put one command per line, and ALWAYS put a line return +# after an alias and before a command, eg: +# +#``` +#all: +# test +# install +#test: +# nosetest +#install: +# python setup.py install +# ``` -alltests: testcoverage flake8 testsetup -all: alltests build +.PHONY: + alltests + +alltests: + testcoverage + flake8 + testsetup + +all: + alltests + build flake8: flake8 --max-line-length=80 --count --statistics --exit-zero tqdm/ @@ -15,6 +36,7 @@ testnose: testsetup: python setup.py check --restructuredtext --strict + python setup.py make none testcoverage: nosetests tqdm --with-coverage --cover-package=tqdm -v @@ -35,3 +57,6 @@ pypimeta: pypi: twine upload dist/* + +none: + none # used for unit testing diff --git a/RELEASE b/RELEASE index 03928fd8..d02b8353 100644 --- a/RELEASE +++ b/RELEASE @@ -5,7 +5,8 @@ This file is intended for the project's maintainers and it describes how to update, build and upload a new release. Most of the management commands have been directly placed inside -the Makefile, so you just have to run `make [alias]`. +the Makefile, so you just have to run `python setup.py make [alias]`, which +is equivalent to using `make [alias]` but without requiring `make`. Note: to use the Makefile on Windows, you need to install make.exe, for example by installing [MinGW MSYS](http://www.mingw.org/wiki/msys). @@ -40,7 +41,8 @@ python setup.py check --restructuredtext --strict ``` Also, if you happen to mistakenly upload a broken release to PyPi, -you can fix the metadata by using: `make pypimeta` or `python setup.py register` +you can fix the metadata by using: `python setup.py make pypimeta` +or `python setup.py register`. BUILDING A RELEASE AND UPLOADING TO PYPI @@ -52,13 +54,13 @@ process and info that will be uploaded to [pypi](pypi.python.org). Check the result by using the following commands: ``` -make installdev +python setup.py make installdev ``` Secondly, build tqdm into a distributable python package: ``` -make build +python setup.py make build ``` This will generate several builds in the `dist/` folder. @@ -67,7 +69,7 @@ Finally, we can upload everything to pypi. Uploading to pypi can be done easily using the [twine](https://github.com/pypa/twine) module: ``` -make pypi +python setup.py make pypi ``` NOTE: diff --git a/setup.py b/setup.py index d3f6e556..008599d3 100755 --- a/setup.py +++ b/setup.py @@ -3,6 +3,112 @@ import os from setuptools import setup +# For Makefile parsing +import ConfigParser +import StringIO +import sys, subprocess + + +### Makefile auxiliary functions ### + + +def parse_makefile_aliases(filepath): + '''Parse a makefile to find commands and substitute variables. + Note that this function is not a total replacement of make, it only + parse aliases. + Expects a makefile with only aliases and ALWAYS a line return between + each command, eg: + + ``` + all: + test + install + test: + nosetest + install: + python setup.py install + ``` + + Returns a dict, with a list of commands for each alias. + ''' + + # -- Parsing the Makefile using ConfigParser + # Adding a fake section to make the Makefile a valid Ini file + ini_str = '[root]\n' + open(filepath, 'r').read() + ini_fp = StringIO.StringIO(ini_str) + # Parse it using ConfigParser + config = ConfigParser.RawConfigParser() + config.readfp(ini_fp) + # Fetch the list of aliases + aliases = config.options('root') + + # -- Extracting commands for each alias + commands = {} + for alias in aliases: + # strip the first line return, and then split by any line return + commands[alias] = config.get('root', alias).lstrip('\n').split('\n') + + # -- Commands substitution + # We loop until we can substitute all aliases by their commands + # What we do is that we check each command of each alias, and + # if there is one command that is to be substituted by an alias, + # we try to do it right away, but if it's not possible because + # this alias himself points to other aliases, then we stop + # and put the current alias back in the queue, which we + # will process again later when we have substituted the + # other aliases. + + # Create the queue of aliases to process + aliases_todo = commands.keys() + # Create the dict that will hold the substituted aliases by their full commands + commands_new = {} + # Loop until we have processed all aliases + while aliases_todo: + # Pick the first alias in the queue + alias = aliases_todo.pop(0) + # Create a new entry in the resulting dict + commands_new[alias] = [] + # For each command of this alias + for cmd in commands[alias]: + # If the alias points to itself, we pass + if cmd == alias: + pass + # If the alias points to a full command, we substitute + elif cmd in aliases and cmd in commands_new: + # Append all the commands referenced by the alias + commands_new[alias].extend(commands_new[cmd]) + # If the alias points to another alias, we delay, + # waiting for the other alias to be substituted first + elif cmd in aliases and cmd not in commands_new: + # Delete the current entry to avoid other aliases + # to reference this one wrongly (as it is empty) + del commands_new[alias] + # Put back into the queue + aliases_todo.append(alias) + # Break the loop for the current alias + break + # Else this is just a full command (no reference to an alias) + # so we just append it + else: + commands_new[alias].append(cmd) + commands = commands_new + del commands_new + # -- Prepending prefix to avoid conflicts with standard setup.py commands + #for alias in commands.keys(): + #commands['make_'+alias] = commands[alias] + #del commands[alias] + return commands + +def execute_makefile_commands(commands, alias, verbose=False): + cmds = commands[alias] + for cmd in cmds: + if verbose: print("Running command: %s" % cmd) + subprocess.check_call(cmd.split()) + + +### Main setup.py config ### + + # Get version from tqdm/_version.py __version__ = None version_file = os.path.join(os.path.dirname(__file__), 'tqdm', '_version.py') @@ -10,6 +116,21 @@ for line in open(version_file).readlines(): if (line.startswith('version_info') or line.startswith('__version__')): exec(line.strip()) +# Executing makefile commands if specified +if len(sys.argv) >= 3 and sys.argv[1].lower().strip() == 'make': + arg = sys.argv[-1] + fpath = 'Makefile' + commands = parse_makefile_aliases(fpath) + if arg == 'none': # unit testing, we do nothing (we just checked the makefile parsing) + sys.exit(0) + elif arg in commands.keys(): # else if the alias exists, we execute its commands + execute_makefile_commands(commands, arg, verbose=True) + else: # else the alias cannot be found + raise Exception("Provided argument cannot be found: make %s" % (arg)) + # Stop the processing of setup.py here + sys.exit(0) # Important to avoid setup.py to spit an error because of the command not being standard + +# Python package config setup( name='tqdm', version=__version__, diff --git a/tox.ini b/tox.ini index c9580961..58b353fe 100644 --- a/tox.ini +++ b/tox.ini @@ -28,3 +28,4 @@ deps = pygments commands = python setup.py check --restructuredtext --metadata --strict + python setup.py make none