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