Main Content

Using Make for a distribution

Archive - Originally posted on "The Horse's Mouth" - 2012-03-17 09:19:26 - Graham Ellis

Although Make has traditionally been used to build C and C++ programs, its uses are far wider - here's a new example in which I've used it to test a whole series of Python programs, and if they all test correctly to build a distribution (tar) file.

Scenario - three Python programs / classes for distribution. Each has some test code - first.py tests from first principles, second.py tests using doctest and third.py tests using unittest. Of course, I would normally be consistent in using one test harness or another, but this is a demonstration.

The makefile (full source [here]):

  version=1.2
  
  pyproject_$(version).tgz: first.tmp second.tmp third.tmp
      tar czf pyproject_$(version).tgz project
      @echo "BUILD COMPLETED"
  
  # Simple Python Program
  
  first.tmp: project/first.py
      python project/first.py
      @touch first.tmp
  
  # Next example uses Python's doctest
  
  second.tmp: project/second.py
      python project/second.py
      @touch second.tmp
  
  # Next example uses Python's unittest
  
  third.tmp: project/third.py
      python project/third.py
      @touch third.tmp
  
  clean:
      @rm -f *.tmp
      @rm -f *.tgz
      @echo "cleaned up"


Each of the Python test classes is written to return a successful status (0, because we're at shell level) if the test passes, and a failed status (1) if the test fails, so that the build will only complete through to distribution if all tests pass.

In first.py (full source [here]), the test I have added onto the end of the test harness is as follows:
  if tpc != 495: exit(1)
  exit(0)

where tpc is my test variable that should end up with a result of "495" if the code runs right.

Doctest return a status line that we can analyse similarly:
  if status.find("failed=0") != -1: return 0
  return 1

(my doctest code is written within a function, thus "return" rather than "exit").
See full source [here]

Unittest automatically exits with a success / failure status, so all I need to run my test is:
  if __name__ == '__main__':
      unittest.main()

See full source [here]

Let's run that - initially with a bug (for testing) in the doctest example:

  wizard:pyproject graham$ make
  python project/first.py
  First Great Western cl 150 160 2
  First Avon and Somerset 81 1
  Faresaver 29 1
  South West Trains cl 159 225 3
  --- TOTAL --- 495 7
  python project/second.py
  **********************************************************************
  File "project/second.py", line 4, in __main__.factorial
  Failed example:
    [factorial(n) for n in range(6)]
  Expected:
    [1, 1, 2, 7, 24, 120]
  Got:
    [1, 1, 2, 6, 24, 120]
  **********************************************************************
  1 items had failures:
    1 of 1 in __main__.factorial
  ***Test Failed*** 1 failures.
  make: *** [second.tmp] Error 1
  wizard:pyproject graham$


Fixing the bug and trying again - you'll note the first test is NOT repeated:

  wizard:pyproject graham$ make
  python project/second.py
  python project/third.py
  ...
  ----------------------------------------------------------------------
  Ran 3 tests in 0.000s
  
  OK
  tar czf pyproject_1.2.tgz project
  BUILD COMPLETED
  wizard:pyproject graham$


and running again:

  wizard:pyproject graham$ make
  make: `pyproject_1.2.tgz' is up to date.
  wizard:pyproject graham$


I'm adding a section on make onto the end of next week's private Python course - the example above will be used during that course. Also happy to spend up to day going through Make on other private courses ...