<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: jmoudev</title>
    <description>The latest articles on DEV Community by jmoudev (@jmoudev).</description>
    <link>https://dev.to/jmoudev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2904227%2Ffeed496d-d5fc-4ec5-8cbd-f6d22e276f54.png</url>
      <title>DEV Community: jmoudev</title>
      <link>https://dev.to/jmoudev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jmoudev"/>
    <language>en</language>
    <item>
      <title>Levelling up your Python project with GNU Make</title>
      <dc:creator>jmoudev</dc:creator>
      <pubDate>Wed, 12 Mar 2025 12:52:09 +0000</pubDate>
      <link>https://dev.to/jmoudev/levelling-up-your-python-project-with-gnu-make-2p5g</link>
      <guid>https://dev.to/jmoudev/levelling-up-your-python-project-with-gnu-make-2p5g</guid>
      <description>&lt;p&gt;Have you ever found yourself running the same commands over and over again to run, test, and install dependencies on your Python project.&lt;/p&gt;

&lt;p&gt;If you haven't heard of &lt;strong&gt;make&lt;/strong&gt; then the answer to that question is probably YES!!!&lt;/p&gt;

&lt;p&gt;The following guide should provide some make basics and useful resources for running make on your Python project.&lt;/p&gt;

&lt;p&gt;All examples in the guide are run on macOS using a Bash terminal with Python 3.13.&lt;/p&gt;

&lt;h1&gt;
  
  
  What Is GNU Make?
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.gnu.org/software/make/" rel="noopener noreferrer"&gt;GNU Make&lt;/a&gt; is a widely used build automation tool, which enables users to define sets of commands (known as rules), to make files (known as targets).&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing GNU Make
&lt;/h2&gt;

&lt;p&gt;Installing make for Linux and Mac should be straightforward via the standard package manager for your Linux OS, or via brew on macOS.&lt;/p&gt;

&lt;p&gt;For guidance for Windows see article: &lt;a href="https://stackoverflow.com/a/32127632" rel="noopener noreferrer"&gt;How to install and use "make" in Windows?&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basics
&lt;/h2&gt;

&lt;p&gt;A project using make usually has a file named Makefile at the project root.&lt;/p&gt;

&lt;p&gt;Let's define a basic rule for target foo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# foobar/Makefile
&lt;/span&gt;&lt;span class="nl"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;Creating file foo
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;foo file contents &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; foo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running make for target foo from the Bash terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make foo
Creating file foo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executing &lt;code&gt;make foo&lt;/code&gt; from the project root runs the rule, creating the target file foo.&lt;/p&gt;

&lt;p&gt;Let's create another rule for bar with a pre-requisite foo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# foobar/Makefile
&lt;/span&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;bar&lt;/span&gt;
&lt;span class="nl"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;bar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make bar
bar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The line &lt;code&gt;bar: foo&lt;/code&gt; states that the file foo is a pre-requisite for bar, and therefore will be created before bar.&lt;/p&gt;

&lt;p&gt;However, notice &lt;code&gt;Creating file foo&lt;/code&gt; is not printed when running &lt;code&gt;make bar&lt;/code&gt;. To demonstrate what's going on here we can run make target foo again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make foo
make: &lt;span class="s1"&gt;'foo'&lt;/span&gt; is up to date.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;foo&lt;/code&gt; is not created as the file was already created via &lt;code&gt;make foo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You may notice .PHONY declaration above bar. This declares the file as a phony target, which is not typically the name of a file, but a rule to be executed when you run make.&lt;/p&gt;

&lt;p&gt;Let's amend target foo and declare it as a phony target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# foobar/Makefile
&lt;/span&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;
&lt;span class="nl"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;foo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make bar
foo
bar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when we run &lt;code&gt;make bar&lt;/code&gt;, foo is printed to the terminal.&lt;/p&gt;

&lt;h1&gt;
  
  
  Python Applications
&lt;/h1&gt;

&lt;p&gt;To demonstrate some common applications let's think about some common user stories which could be defined in a Makefile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As a user I want to run my project&lt;/li&gt;
&lt;li&gt;As a developer I want to set up the project for development&lt;/li&gt;
&lt;li&gt;As a developer I want to run the tests locally&lt;/li&gt;
&lt;li&gt;As a developer I want CI to test across a range of Python versions&lt;/li&gt;
&lt;li&gt;As a developer I want CI to build the documentation&lt;/li&gt;
&lt;li&gt;As a developer I want CI to perform lint and formatting checks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  User
&lt;/h2&gt;

&lt;p&gt;Provided a basic script &lt;code&gt;main.py&lt;/code&gt; which should print "Hello, world!", we can easily run the script via make:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# hello-world/main.py
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, world!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# hello-world/Makefile
&lt;/span&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;
&lt;span class="nl"&gt;run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make run
Hello, world!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As well as for Python scripts, the &lt;code&gt;run&lt;/code&gt; target could be useful for any Python application which takes basic args such as a Flask server.&lt;/p&gt;

&lt;p&gt;In the example where we have a CLI application where there's a specific command to run the application, it may be more useful to include a &lt;code&gt;help&lt;/code&gt; target to print the CLI helper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer
&lt;/h2&gt;

&lt;p&gt;When developing on a shared / open source project there are often coding standards to follow, and tests to be run before committing code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developer Setup
&lt;/h3&gt;

&lt;p&gt;Pre-requisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A project using &lt;a href="https://git-scm.com" rel="noopener noreferrer"&gt;git&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pre-commit.com/#2-add-a-pre-commit-configuration" rel="noopener noreferrer"&gt;Pre-commit configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A minimal setup.py or pyproject.toml file, see &lt;a href="https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#a-full-example" rel="noopener noreferrer"&gt;example pyproject.toml&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Compatible build-backend for editable builds: see &lt;a href="https://stackoverflow.com/a/69711730" rel="noopener noreferrer"&gt;How to install a package using pip in editable mode with pyproject.toml?&lt;/a&gt; for pyproject.toml compatible versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This project makes use of pre-commit to enforce coding standards upon commit (&lt;a href="https://pre-commit.com" rel="noopener noreferrer"&gt;pre-commit hooks&lt;/a&gt;), as well as &lt;a href="https://docs.pytest.org/en/stable/" rel="noopener noreferrer"&gt;pytest&lt;/a&gt; testing framework.&lt;/p&gt;

&lt;p&gt;The target make dev, installs the required Python packages for development, and installs the pre-commit hooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# hello-world/Makefile
&lt;/span&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dev&lt;/span&gt;
&lt;span class="nl"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; pre-commit pytest
    pre-commit &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--install-hooks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make dev
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nt"&gt;--require-virtualenv&lt;/span&gt; &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; pre-commit pytest
...
Successfully installed main-0.0.0
pre-commit &lt;span class="nb"&gt;install
&lt;/span&gt;pre-commit installed at .git/hooks/pre-commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above example also utilizes &lt;a href="https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs" rel="noopener noreferrer"&gt;editable mode&lt;/a&gt; which enable us to make changes and test these changes without re-installing the application / package(s) to our local venv.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Locally
&lt;/h3&gt;

&lt;p&gt;Pre-requisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;make dev&lt;/code&gt; to install &lt;code&gt;pytest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Some unit tests in the &lt;code&gt;tests/&lt;/code&gt; directory at the project root
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# hello-world/Makefile
&lt;/span&gt;&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;
&lt;span class="nl"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    pytest tests/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make &lt;span class="nb"&gt;test
&lt;/span&gt;pytest tests/
...
test_main.py &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CI Setup
&lt;/h2&gt;

&lt;p&gt;For larger shared / open source projects you may encounter a continuous integration (CI) pipeline, which can run a number of checks on code before it is merged into the main branch. &lt;/p&gt;

&lt;p&gt;There are many pre-requisites for setting up CI on a repository which will not be covered in this article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make install-ci
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;mypy ruff tox
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lint and Type Checking
&lt;/h3&gt;

&lt;p&gt;Some examples of rules for lint and type checking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make lint
ruff check &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--fix&lt;/span&gt;
All checks passed!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make &lt;span class="nb"&gt;type
&lt;/span&gt;mypy main.py
Success: no issues found &lt;span class="k"&gt;in &lt;/span&gt;1 &lt;span class="nb"&gt;source &lt;/span&gt;file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing Across Multiple Python Versions
&lt;/h3&gt;

&lt;p&gt;Pre-requisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tox.wiki/en/stable/config.html" rel="noopener noreferrer"&gt;tox config&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Available Python versions as specified&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We may want to run the tests in ci across a range of Python versions with tox:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make test-all
tox run-parallel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Further Suggestions
&lt;/h2&gt;

&lt;p&gt;Further suggestions for improving your Makefile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add an environment variable (i.e. PYTEST_ARGS) to pytest command to allow passing in the specific test file / test&lt;/li&gt;
&lt;li&gt;Measure tests coverage (pytest-cov)&lt;/li&gt;
&lt;li&gt;Build documentation (sphinx)&lt;/li&gt;
&lt;li&gt;Publish the project to PyPI (twine)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By adding a Makefile to your Python project, you can streamline so many aspects of development. &lt;/p&gt;

&lt;p&gt;Basic tasks like running the project or tests, linting, and packaging your project and more can be standardized with a simple Makefile. This saves time and ensures consistency across your development workflow, as well as reducing the barrier to entry for developers to contribute to your project.&lt;/p&gt;

&lt;p&gt;As you grow and share your Python projects, &lt;strong&gt;make&lt;/strong&gt; can be invaluable to maintain consistent and efficient development process.&lt;/p&gt;

&lt;p&gt;The examples in this guide and more are available in the following GitHub repository: &lt;a href="https://github.com/jmoudev/python-make-examples" rel="noopener noreferrer"&gt;https://github.com/jmoudev/python-make-examples&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>python</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
