[Distutils] abstract build system approaches redux

Donald Stufft donald at stufft.io
Thu Mar 3 08:44:56 EST 2016


> On Mar 3, 2016, at 8:19 AM, Nick Coghlan <ncoghlan at gmail.com> wrote:
> 
> On 3 March 2016 at 10:43, Robert Collins <robertc at robertcollins.net> wrote:
>> Python vs CLI - I don't care enough to argue. I think a CLI is better
>> in this space, as running commands is the lingua franca of CLI's;
>> previously Donald has advocated for that as well, but his proposal is
>> now a Python API. *shrug*. I want the BDFL-Delegate to choose.
> 
> I'm happy to let the discussion run for the other open questions you
> mention, but I'm now prepared to pronounce on this aspect based on one
> specific factor: I don't want to mandate that all future build systems
> for Python projects (whether front ends or back ends) must be written
> in Python.
> 
> If somebody comes up with an all-singing, all-dancing Python build
> system that happens to be written in Go, or Rust, or Haskell, or that
> they made by adapting an existing build system for another language
> ecosystem to have native support for also building Python packages,
> I'd like for the Python packaging ecosystem to be able to handle that
> without significant fuss.
> 
> Attaining that level of cross-language interoperability then
> necessarily means defining the formal build system abstraction
> interface at the CLI boundary, rather than as a Python API.
> 
> Defining a helper library/framework to make it *easier* to write new
> build systems in Python would still make sense, but that can be done
> in the usual way that any other library gains widespread acceptance:
> by being easier to use than a "DIY" approach when it comes to directly
> implementing the underlying interoperability specification.


I'd like to push back against this, speaking as someone who was originally pro
CLI:

I think that a Python API is actually better for one reason: introspection.
I cannot think of a particularly great way to have a CLI based build tool
*evolve* with new APIs that are not user facing without requiring end users
to do something like mark "ok now my thing is X compatible" or without
inventing some sort of protocol negotiation phase.

For instance, let's say that we say that we need an ``metadata`` API that can
output metadata 1.x style metadata. That's great and right now we can just
assume that it exists. However, let's say in the future we add a metadata 2.0
spec that isn't backwards compatible. Now how do we handle this? A new version
of pip can't just assume that the ``metadata`` command is going to always
return 2.x metadata now, because that would break all existing build tools so
it needs some method to determine what capabilities a particular build system
has.

I see a few options:

* If we're using a CLI based thing, we need to create a negotiation phase,
  which I think is a bad idea because it's kind of complicated and error prone.
  Looking at TLS or HTTP, there's more than one bug that exists because of
  this.

* If we're using a CLI based thing, we need to bake into the file what the
  capabilities of the build system is. Maybe using something like:

    generate-metadata-2: true
    # OR
    metadata2-generation-command: ${PYTHON} -m generate-metdata

  However, I think this is bad because it makes it harder to get automatic
  "wins" for new features because you have to convince every single author to
  go and modify their file to tell pip (or whatever) that this new feature is
  available as well as then needing to force a >= in their build dependencies
  because they mandating some new feature by baking it directly into their
  project.

* If we're using a Python based API, we can simply just say that there can be
  no backwards incompatible changes made to an API name once defined. If we
  want to create a new function that produces the new style metadata, then we
  can simply do a check like:

    if hasattr(api, "metadata2"):
        # Do Metadata 2.x Code
    else:
        # Do Metadata 1.x Code

  This not only allows all projects to automatically start using this new
  feature as soon as a build tool implements it, getting automatic wins, but it
  also makes it trivial for a project to support both old and new versions of
  pip, since there will be a different API name for a backwards incompatible
  change. Old pip would just use the old code, and new pip would use the new
  code.


The only real benefits I can see to using a CLI based over a Python based are:

* We move some code out of pip into each individual build backend, making it
  clearer the process boundaries. This is a nice thing, and it is the primary
  reason I wanted a CLI based API. However I don't think it outweighs the
  ability to introspect what API methods are available.

* We make it easier to use a non Python based build system. This is also a nice
  thing, however I don't think it should be a major decider in what API we
  provide. Any reasonable build system is going to have to be available via
  ``pip install`` in some fashion, so even if you write your build system in
  Go or Rust, you're going to have to create a Python package for it anyways,
  and if you're doing that, adding a tiny shim is pretty trivial, something
  like:

    import os.path
    import subprocess

    BIN = os.path.join(os.path.dirname(__file__), "mytool")

    def some_api_function(*args, **kwargs):
        flags = convert_args_kwargs_to_flags(*args, **kwargs)
        subprocess.run(BIN, *flags, check=True)

  I don't believe it to be a substantial burden to need to write a tiny wrapper
  if you're going to do something which I believe is going to be very unlikely.


In the end, I think this comes down that we shouldn't optimize for the least
common case, at the expense of the ability to more easily evolve the API in the
future.

-----------------
Donald Stufft
PGP: 0x6E3CBCE93372DCFA // 7C6B 7C5D 5E2B 6356 A926 F04F 6E3C BCE9 3372 DCFA

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 842 bytes
Desc: Message signed with OpenPGP using GPGMail
URL: <http://mail.python.org/pipermail/distutils-sig/attachments/20160303/cc827984/attachment-0001.sig>


More information about the Distutils-SIG mailing list