1.. _Creating ``distutils`` Extensions:
2
3Creating ``distutils`` Extensions
4=================================
5
6It can be hard to add new commands or setup arguments to the distutils.  But
7the ``setuptools`` package makes it a bit easier, by allowing you to distribute
8a distutils extension as a separate project, and then have projects that need
9the extension just refer to it in their ``setup_requires`` argument.
10
11With ``setuptools``, your distutils extension projects can hook in new
12commands and ``setup()`` arguments just by defining "entry points".  These
13are mappings from command or argument names to a specification of where to
14import a handler from.  (See the section on :ref:`Dynamic Discovery of
15Services and Plugins` above for some more background on entry points.)
16
17
18Adding Commands
19---------------
20
21You can add new ``setup`` commands by defining entry points in the
22``distutils.commands`` group.  For example, if you wanted to add a ``foo``
23command, you might add something like this to your distutils extension
24project's setup script::
25
26    setup(
27        # ...
28        entry_points={
29            "distutils.commands": [
30                "foo = mypackage.some_module:foo",
31            ],
32        },
33    )
34
35(Assuming, of course, that the ``foo`` class in ``mypackage.some_module`` is
36a ``setuptools.Command`` subclass.)
37
38Once a project containing such entry points has been activated on ``sys.path``,
39(e.g. by running "install" or "develop" with a site-packages installation
40directory) the command(s) will be available to any ``setuptools``-based setup
41scripts.  It is not necessary to use the ``--command-packages`` option or
42to monkeypatch the ``distutils.command`` package to install your commands;
43``setuptools`` automatically adds a wrapper to the distutils to search for
44entry points in the active distributions on ``sys.path``.  In fact, this is
45how setuptools' own commands are installed: the setuptools project's setup
46script defines entry points for them!
47
48.. note::
49   When creating commands, and specially when defining custom ways of building
50   compiled extensions (for example via ``build_ext``), consider
51   handling exceptions such as ``CompileError``, ``LinkError``, ``LibError``,
52   among others.  These exceptions are available in the ``setuptools.errors``
53   module.
54
55
56Adding ``setup()`` Arguments
57----------------------------
58
59.. warning:: Adding arguments to setup is discouraged as such arguments
60   are only supported through imperative execution and not supported through
61   declarative config.
62
63Sometimes, your commands may need additional arguments to the ``setup()``
64call.  You can enable this by defining entry points in the
65``distutils.setup_keywords`` group.  For example, if you wanted a ``setup()``
66argument called ``bar_baz``, you might add something like this to your
67distutils extension project's setup script::
68
69    setup(
70        # ...
71        entry_points={
72            "distutils.commands": [
73                "foo = mypackage.some_module:foo",
74            ],
75            "distutils.setup_keywords": [
76                "bar_baz = mypackage.some_module:validate_bar_baz",
77            ],
78        },
79    )
80
81The idea here is that the entry point defines a function that will be called
82to validate the ``setup()`` argument, if it's supplied.  The ``Distribution``
83object will have the initial value of the attribute set to ``None``, and the
84validation function will only be called if the ``setup()`` call sets it to
85a non-None value.  Here's an example validation function::
86
87    def assert_bool(dist, attr, value):
88        """Verify that value is True, False, 0, or 1"""
89        if bool(value) != value:
90            raise DistutilsSetupError(
91                "%r must be a boolean value (got %r)" % (attr,value)
92            )
93
94Your function should accept three arguments: the ``Distribution`` object,
95the attribute name, and the attribute value.  It should raise a
96``DistutilsSetupError`` (from the ``distutils.errors`` module) if the argument
97is invalid.  Remember, your function will only be called with non-None values,
98and the default value of arguments defined this way is always None.  So, your
99commands should always be prepared for the possibility that the attribute will
100be ``None`` when they access it later.
101
102If more than one active distribution defines an entry point for the same
103``setup()`` argument, *all* of them will be called.  This allows multiple
104distutils extensions to define a common argument, as long as they agree on
105what values of that argument are valid.
106
107Also note that as with commands, it is not necessary to subclass or monkeypatch
108the distutils ``Distribution`` class in order to add your arguments; it is
109sufficient to define the entry points in your extension, as long as any setup
110script using your extension lists your project in its ``setup_requires``
111argument.
112
113
114Customizing Distribution Options
115--------------------------------
116
117Plugins may wish to extend or alter the options on a Distribution object to
118suit the purposes of that project. For example, a tool that infers the
119``Distribution.version`` from SCM-metadata may need to hook into the
120option finalization. To enable this feature, Setuptools offers an entry
121point "setuptools.finalize_distribution_options". That entry point must
122be a callable taking one argument (the Distribution instance).
123
124If the callable has an ``.order`` property, that value will be used to
125determine the order in which the hook is called. Lower numbers are called
126first and the default is zero (0).
127
128Plugins may read, alter, and set properties on the distribution, but each
129plugin is encouraged to load the configuration/settings for their behavior
130independently.
131
132
133.. _Adding new EGG-INFO Files:
134
135Adding new EGG-INFO Files
136-------------------------
137
138Some extensible applications or frameworks may want to allow third parties to
139develop plugins with application or framework-specific metadata included in
140the plugins' EGG-INFO directory, for easy access via the ``pkg_resources``
141metadata API.  The easiest way to allow this is to create a distutils extension
142to be used from the plugin projects' setup scripts (via ``setup_requires``)
143that defines a new setup keyword, and then uses that data to write an EGG-INFO
144file when the ``egg_info`` command is run.
145
146The ``egg_info`` command looks for extension points in an ``egg_info.writers``
147group, and calls them to write the files.  Here's a simple example of a
148distutils extension defining a setup argument ``foo_bar``, which is a list of
149lines that will be written to ``foo_bar.txt`` in the EGG-INFO directory of any
150project that uses the argument::
151
152    setup(
153        # ...
154        entry_points={
155            "distutils.setup_keywords": [
156                "foo_bar = setuptools.dist:assert_string_list",
157            ],
158            "egg_info.writers": [
159                "foo_bar.txt = setuptools.command.egg_info:write_arg",
160            ],
161        },
162    )
163
164This simple example makes use of two utility functions defined by setuptools
165for its own use: a routine to validate that a setup keyword is a sequence of
166strings, and another one that looks up a setup argument and writes it to
167a file.  Here's what the writer utility looks like::
168
169    def write_arg(cmd, basename, filename):
170        argname = os.path.splitext(basename)[0]
171        value = getattr(cmd.distribution, argname, None)
172        if value is not None:
173            value = "\n".join(value) + "\n"
174        cmd.write_or_delete_file(argname, filename, value)
175
176As you can see, ``egg_info.writers`` entry points must be a function taking
177three arguments: a ``egg_info`` command instance, the basename of the file to
178write (e.g. ``foo_bar.txt``), and the actual full filename that should be
179written to.
180
181In general, writer functions should honor the command object's ``dry_run``
182setting when writing files, and use the ``distutils.log`` object to do any
183console output.  The easiest way to conform to this requirement is to use
184the ``cmd`` object's ``write_file()``, ``delete_file()``, and
185``write_or_delete_file()`` methods exclusively for your file operations.  See
186those methods' docstrings for more details.
187
188
189.. _Adding Support for Revision Control Systems:
190
191Adding Support for Revision Control Systems
192-------------------------------------------------
193
194If the files you want to include in the source distribution are tracked using
195Git, Mercurial or SVN, you can use the following packages to achieve that:
196
197- Git and Mercurial: :pypi:`setuptools_scm`
198- SVN: :pypi:`setuptools_svn`
199
200If you would like to create a plugin for ``setuptools`` to find files tracked
201by another revision control system, you can do so by adding an entry point to
202the ``setuptools.file_finders`` group.  The entry point should be a function
203accepting a single directory name, and should yield all the filenames within
204that directory (and any subdirectories thereof) that are under revision
205control.
206
207For example, if you were going to create a plugin for a revision control system
208called "foobar", you would write a function something like this:
209
210.. code-block:: python
211
212    def find_files_for_foobar(dirname):
213        ...  # loop to yield paths that start with `dirname`
214
215And you would register it in a setup script using something like this::
216
217    entry_points={
218        "setuptools.file_finders": [
219            "foobar = my_foobar_module:find_files_for_foobar",
220        ]
221    }
222
223Then, anyone who wants to use your plugin can simply install it, and their
224local setuptools installation will be able to find the necessary files.
225
226It is not necessary to distribute source control plugins with projects that
227simply use the other source control system, or to specify the plugins in
228``setup_requires``.  When you create a source distribution with the ``sdist``
229command, setuptools automatically records what files were found in the
230``SOURCES.txt`` file.  That way, recipients of source distributions don't need
231to have revision control at all.  However, if someone is working on a package
232by checking out with that system, they will need the same plugin(s) that the
233original author is using.
234
235A few important points for writing revision control file finders:
236
237* Your finder function MUST return relative paths, created by appending to the
238  passed-in directory name.  Absolute paths are NOT allowed, nor are relative
239  paths that reference a parent directory of the passed-in directory.
240
241* Your finder function MUST accept an empty string as the directory name,
242  meaning the current directory.  You MUST NOT convert this to a dot; just
243  yield relative paths.  So, yielding a subdirectory named ``some/dir`` under
244  the current directory should NOT be rendered as ``./some/dir`` or
245  ``/somewhere/some/dir``, but *always* as simply ``some/dir``
246
247* Your finder function SHOULD NOT raise any errors, and SHOULD deal gracefully
248  with the absence of needed programs (i.e., ones belonging to the revision
249  control system itself.  It *may*, however, use ``distutils.log.warn()`` to
250  inform the user of the missing program(s).
251