# Proper way to determine install location of console scripts

**URL:** <https://discuss.python.org/t/proper-way-to-determine-install-location-of-console-scripts/7188>\
**Category:** Packaging\
**Created:** [February 12, 2021, 5:44pm UTC](https://discuss.python.org/t/proper-way-to-determine-install-location-of-console-scripts/7188 "2021-02-12T17:44:44Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![jwodder](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jwodder/32/236_2.png) [@jwodder](https://discuss.python.org/u/jwodder)\
**Post date:** [February 12, 2021, 5:44pm UTC](https://discuss.python.org/t/proper-way-to-determine-install-location-of-console-scripts/7188/1 "2021-02-12T17:44:44Z")

</div>

I’m writing a script for automating installations of several packages, and it checks that the relevant command-line programs are where they’re expected to be after installation. Unfortunately, I cannot determine a foolproof way of always predicting exactly where pip will place console scripts. According to [these](https://stackoverflow.com/q/64183571/744178) [two](https://stackoverflow.com/q/62162970/744178) StackOverflow questions, the rules are as follows:

- For user installs: `sysconfig.get_path('scripts', f'{os.name}_user')` (which — always? — equals `os.path.join(user_site, 'bin')`, or `os.path.join(user_site, 'Scripts')` on Windows)
- For non-user installs: `sysconfig.get_path('scripts')` (which — always? — equals  
`os.path.join(sys.prefix, 'bin')`, or `os.path.join(sys.prefix, 'Scripts')` on Windows), with a special exception that maps `/System/Library/` paths on macOS to `/usr/local/bin`.

However, this is not the whole story, and I cannot find the rest of the story in the pip code. There are at least two situations where the above rules are incorrect:

- When using Python installed through Homebrew on macOS, the predicted system install path is of the form `/usr/local/Cellar/python@3.9/3.9.1_8/Frameworks/Python.framework/Versions/3.9/bin`, but console scripts are actually installed in `/usr/local/bin`.
- On Ubuntu 20.04 (and possibly other versions), the predicted system install path is `/usr/bin`, but console scripts are actually installed in `/usr/local/bin`.

What are the exact rules to follow to take exceptions like these into account?

---

<div class="post-metadata">

**Author:** ![uranusjr](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/uranusjr/32/103_2.png) [@uranusjr](https://discuss.python.org/u/uranusjr)\
**Post date:** [February 12, 2021, 6:07pm UTC](https://discuss.python.org/t/proper-way-to-determine-install-location-of-console-scripts/7188/2 "2021-02-12T18:07:59Z")

</div>

For historical reasons, pip uses distutils to get console script locations instead of sysconfig. The two should return the same values, but some systems (incorrectly) patch only one of them and not the other. That’s why you see this on Ubuntu (the behaviour is inherited from Debian). Not sure about Homebrew.

---

<div class="post-metadata">

**Author:** ![encukou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/encukou/32/2461_2.png) [@encukou](https://discuss.python.org/u/encukou)\
**Post date:** [February 12, 2021, 7:02pm UTC](https://discuss.python.org/t/proper-way-to-determine-install-location-of-console-scripts/7188/3 "2021-02-12T19:02:05Z")

</div>

> but some systems (incorrectly) patch only one of them and not the other

Sadly, there’s not much guidance on what exactly the `sysconfig` values should mean, or on how distros like Debian or Homebrew _should_ correct them if they’re wrong.  
I guess it’s historically due to lack of communication – both distros and CPython doing stuff their own way, while privately accusing the other party of doing stuff incorrectly.

---

<div class="post-metadata">

**Author:** ![uranusjr](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/uranusjr/32/103_2.png) [@uranusjr](https://discuss.python.org/u/uranusjr)\
**Post date:** [February 12, 2021, 7:10pm UTC](https://discuss.python.org/t/proper-way-to-determine-install-location-of-console-scripts/7188/4 "2021-02-12T19:10:30Z")

</div>

Indeed. In the case of `sysconfig` specifically, the lack of communication is not only between CPython and Linux distributions, but also between PyPA and CPython core developers. Many packaging contributors were not aware the two modules are returning different things (or even there’s a second way to get the values at all!) until much too late, missing the opportunity to communicate the issue to relevant parties.

---

<div class="post-metadata">

**Author:** ![jwodder](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jwodder/32/236_2.png) [@jwodder](https://discuss.python.org/u/jwodder)\
**Post date:** [February 12, 2021, 7:28pm UTC](https://discuss.python.org/t/proper-way-to-determine-install-location-of-console-scripts/7188/5 "2021-02-12T19:28:38Z")

</div>

So, after some more digging through pip’s code, I’ve determined that the code block highlighted in [this SO answer](https://stackoverflow.com/a/64395375/744178) is only relevant when _uninstalling_ scripts, not when installing them. When installing, the script directory equals `pip._internal.locations.get_scheme("package-name", **kwargs).scripts` for the appropriate `**kwargs` settings. This, in turn, determines the paths by calling distutils, which is where Ubuntu inserts its patch to make pip install in `/usr/local/bin` instead of `/usr/bin`. I can’t find where Homebrew does its patching, but it must be somewhere, as `get_scheme("foo").scripts` equals `/usr/local/bin` on Homebrew Python.

Now I have to decide whether to make use of this logic by calling a private function in pip or by using a standard library module that’s slated for removal …

---

<div class="post-metadata">

**Author:** ![bernatgabor](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bernatgabor/32/3003_2.png) [@bernatgabor](https://discuss.python.org/u/bernatgabor)\
**Post date:** [February 12, 2021, 7:29pm UTC](https://discuss.python.org/t/proper-way-to-determine-install-location-of-console-scripts/7188/6 "2021-02-12T19:29:11Z")

</div>

For what it’s worth in python3.10+ distutils is removed so now there’s only sysconfig left…

---

<div class="post-metadata">

**Author:** ![steve.dower](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steve.dower/32/56_2.png) [@steve.dower](https://discuss.python.org/u/steve.dower)\
**Post date:** [February 12, 2021, 7:33pm UTC](https://discuss.python.org/t/proper-way-to-determine-install-location-of-console-scripts/7188/7 "2021-02-12T19:33:54Z")

</div>

> [@bernatgabor](#):
>
> For what it’s worth in python3.10+

Python 3.12, but yeah, you’ll get deprecation warnings in tests if you try and keep using the deprecated one.

---

<div class="post-metadata">

**Author:** ![encukou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/encukou/32/2461_2.png) [@encukou](https://discuss.python.org/u/encukou)\
**Post date:** [February 12, 2021, 7:52pm UTC](https://discuss.python.org/t/proper-way-to-determine-install-location-of-console-scripts/7188/8 "2021-02-12T19:52:08Z")

</div>

> [@jwodder](#):
>
> Now I have to decide whether to make use of this logic by calling a private function in pip or by using a standard library module that’s slated for removal …

- As part of the removal, we’ll try to [make things better](https://github.com/python/cpython/pull/23142). Alas, that doesn’t really help right now.
- `distutils` will most likely continue as part of Setuptools. If you don’t mind the dependency, you can plan to switch to that version.

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [February 12, 2021, 9:23pm UTC](https://discuss.python.org/t/proper-way-to-determine-install-location-of-console-scripts/7188/9 "2021-02-12T21:23:58Z")

</div>

> [@jwodder](#):
>
> Now I have to decide whether to make use of this logic by calling a private function in pip or by using a standard library module that’s slated for removal

It’s quite likely that pip will switch to `sysconfig` at some point, for what it’s worth (and when we do that, the internal function might be modified or even removed, so be careful if you take that route!) But the real problem here is that `distutils` and `sysconfig` don’t give the same answer, which is ultimately a bug (in the stdlib, or in the way distributions are patching the stdlib, I’m not entirely clear from what you said).

As a workaround for that bug, can’t you check that the console scripts at least end up in _one_ of those locations? It’s not ideal, but it’s probably as good as anything else (after all, installers other than pip might not choose the same stdlib function as pip does).
