Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

python@3.10: migrate aliases #87075

Closed
wants to merge 6 commits into from

Conversation

XuehaiPan
Copy link
Contributor

  • Have you followed the guidelines for contributing?
  • Have you ensured that your commits follow the commit style guide?
  • Have you checked that there aren't other open pull requests for the same formula update/change?
  • Have you built your formula locally with brew install --build-from-source <formula>, where <formula> is the name of the formula you're submitting?
  • Is your test running fine brew test <formula>, where <formula> is the name of the formula you're submitting?
  • Does your build pass brew audit --strict <formula> (after doing brew install --build-from-source <formula>)? If this is a new formula, does it pass brew audit --new <formula>?

Migrate aliases (python, python3, python@3) to python@3.10.

@BrewTestBot BrewTestBot added the automerge-skip `brew pr-automerge` will skip this pull request label Oct 11, 2021
@carlocab
Copy link
Member

Not sure this is ready yet, as python@3.10 is still keg-only. Open to other opinions, however.

@carlocab carlocab added the maintainer feedback Additional maintainers' opinions may be needed label Oct 11, 2021
@carlocab
Copy link
Member

Sorry, when I said this isn't ready because python@3.10 is still keg-only, I didn't mean make python@3.9 keg-only so that we can make python@3.10 not-keg-only.

We should wait until most/all Python-dependent formulae have been migrated to python@3.10 first. Currently only a small (but significant) fraction have been migrated.

@XuehaiPan
Copy link
Contributor Author

Sorry, when I said this isn't ready because python@3.10 is still keg-only, I didn't mean make python@3.9 keg-only so that we can make python@3.10 not-keg-only.

We should wait until most/all Python-dependent formulae have been migrated to python@3.10 first. Currently only a small (but significant) fraction have been migrated.

I fully understand that. I pushed more commits here and we can merge these changes in a single PR when we are ready (maybe 2 weeks later).

@XuehaiPan
Copy link
Contributor Author

Oh, the review is not requested by me. We are probably not ready for this yet. The migration for python@3.9 took about 3 weeks (Python 3.9 was released on 2020-10-05 (Active Python Releases), and the migration for Python 3 aliases (a4d29eb) was committed at 2020-10-28).

@MikeMcQuaid MikeMcQuaid removed their request for review October 18, 2021 15:16
@github-actions
Copy link
Contributor

github-actions bot commented Nov 9, 2021

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@github-actions github-actions bot added the stale No recent activity label Nov 9, 2021
@XuehaiPan
Copy link
Contributor Author

XuehaiPan commented Nov 9, 2021

The migration for python@3.9 took about 3 weeks.

The migration process for python@3.10 takes longer than expected since we have more formulae than a year ago. Be patient.

$ git rev-parse HEAD  # current HEAD
ec64d6dd8537b662ecb84a500508a89aa12e77fe

$ for formula in $(find Formula -name 'python@*.rb' | xargs basename -s .rb | sort -rV); do echo "$(grep -rlF 'depends_on "'"${formula}"'"' Formula | wc -l)\tformulae depend on ${formula}"; done
268     formulae depend on python@3.10
335     formulae depend on python@3.9
11      formulae depend on python@3.8
3       formulae depend on python@3.7

$ git checkout a4d29eb6431ac9b57f07d6b29f3c5a1238ef2370
Note: switching to 'a4d29eb6431ac9b57f07d6b29f3c5a1238ef2370'.
HEAD is now at a4d29eb6431 python@3.9: migrate aliases

$ for formula in $(find Formula -name 'python@*.rb' | xargs basename -s .rb | sort -rV); do echo "$(grep -rlF 'depends_on "'"${formula}"'"' Formula | wc -l)\tformulae depend on ${formula}"; done
425     formulae depend on python@3.9
22      formulae depend on python@3.8
5       formulae depend on python@3.7

@github-actions github-actions bot removed the stale No recent activity label Nov 9, 2021
@carlocab
Copy link
Member

carlocab commented Nov 9, 2021

I wouldn't count on the remaining migration being completed in three weeks. It might, but I also think it'll take longer than the first batch of formulae that's been migrated, since most of the remaining formulae to be migrated live in a few large dependency trees that need to be migrated together. That will take some work.

@carlocab
Copy link
Member

❯ git rev-parse HEAD
252d93c00df9a7e2d809c9fbd723de7334dc8379

❯ for formula in $(find Formula -name 'python@*.rb' | xargs basename -s .rb | sort -rV); do echo "$(grep -rF 'depends_on "'"${formula}"'"' Formula | wc -l)\tformulae depend on ${formula}"; done
274     formulae depend on python@3.10
331     formulae depend on python@3.9
11      formulae depend on python@3.8
3       formulae depend on python@3.7

Not that much movement in the past ten days.

@XuehaiPan
Copy link
Contributor Author

Any updates? It has been two months since Python 3.10 was released.

@SMillerDev
Copy link
Member

Running the script now gives 10 more formula on 3.10. You can't just wait here, if you want this to happen soon you'll need to help migrate them.

     284\tformulae depend on python@3.10
     325\tformulae depend on python@3.9
      11\tformulae depend on python@3.8
       3\tformulae depend on python@3.7

@SMillerDev SMillerDev mentioned this pull request Dec 7, 2021
@cclauss
Copy link
Contributor

cclauss commented Dec 7, 2021

Please add label: python-3.10-migration so that those who want to help get to Python 3.10 know how to discover which formulae to work on.

@XuehaiPan
Copy link
Contributor Author

I think we could open an issue to track the progress of the Python 3.10 migration process.

Should we migrate formula-by-formula in a single pull request or migrate bunch of formulae in a single pull request (test them at once)?

@XuehaiPan

This comment has been minimized.

@carlocab
Copy link
Member

carlocab commented Dec 7, 2021

It's not quite that simple, since dependency trees need to be migrated together. See #87277

@XuehaiPan
Copy link
Contributor Author

XuehaiPan commented Dec 7, 2021

It's not quite that simple, since dependency trees need to be migrated together. See #87277

UPDATE: There are 7 pull requests needed in total:

  • 1 PR with 111 stand-alone formulae
  • 5 PRs with 2 formulae
  • 1 PR with 202 formulae

HEAD: d99ad28


Python code to produce:

#!/usr/bin/env python3

# %%
import json
import shutil
from collections import defaultdict, deque
from subprocess import check_output, run, DEVNULL, PIPE
from typing import OrderedDict

from toposort import toposort  # pip install toposort

brew = shutil.which('brew')
assert brew is not None, 'Could not find `brew` executable in PATH.'
git = shutil.which('git')
assert git is not None, 'Could not find `git` executable in PATH'
HOMEBREW_CORE_REPOSITORY = check_output([brew, '--repository', 'homebrew/core']).decode().strip()

HEAD = check_output([git, '-C', HOMEBREW_CORE_REPOSITORY, 'rev-parse', 'HEAD']).decode().strip()

print(f'brew: {brew}')
print(f'git:  {git}')
print(f'HEAD: {HEAD}')


# %%
def brew_uses(formula):
    # Directly call `brew uses ...` is too slow
    # Use `grep` command instead
    files = run(['grep', '-rlF', f'depends_on "{formula}"'],
                cwd=f'{HOMEBREW_CORE_REPOSITORY}/Formula',
                stdout=PIPE, stderr=DEVNULL).stdout.decode().split()
    return sorted(map(lambda rubyfile: rubyfile[:-3], files))


ALL_FORMULAE = brew_uses('python@3.9')
ALL_FORMULAE.remove('python-tk@3.9')
ALL_FORMULAE_SET = set(ALL_FORMULAE)

USES = dict()
DEPENDENCIES = {formula: set() for formula in ALL_FORMULAE}
for formula in ALL_FORMULAE:
    USES[formula] = sorted(set(brew_uses(formula)) & ALL_FORMULAE_SET)
    for downstream in USES[formula]:
        DEPENDENCIES[downstream].add(formula)


# %%
def flatten(nested):
    flattened = []
    for item in nested:
        if not isinstance(item, str) and hasattr(nested, '__iter__'):
            flattened.extend(flatten(item))
        else:
            flattened.append(item)
    return flattened


def topo_order(dependencies):
    # Topological sort (in the same level, sort by number of downstream nodes)
    return list(map(lambda fl: sorted(fl, key=lambda f: len(USES[f]), reverse=True), toposort(dependencies)))


def topo_depends(formula):
    dependencies = defaultdict(set)
    q = deque([formula])
    q.extend(USES[formula])
    q.extend(DEPENDENCIES[formula])
    while q:
        f = q.popleft()
        if f in dependencies:
            continue
        dependencies[f] = DEPENDENCIES[f]
        q.extend(USES[f])
        q.extend(DEPENDENCIES[f])
    return topo_order(dependencies)


families = []
family_roots = set(topo_order(DEPENDENCIES)[0])
while family_roots:
    root = family_roots.pop()
    family = topo_depends(root)
    family_roots.difference_update(family[0])
    families.append(family)


# %%
results = OrderedDict({'stand-alones': []})
for family in sorted(families, key=lambda family: (len(flatten(family)), family[0][0])):
    family_flattened = flatten(family)
    n_formulae = len(family_flattened)
    if len(family_flattened) > 1:
        roots = family[0]
        results[roots[0]] = family
    else:
        results['stand-alones'].extend(family_flattened)

results['stand-alones'] = [results['stand-alones']]


# %%
for i, (root, family) in enumerate(results.items(), start=1):
    family_flattened = flatten(family)
    n_formulae = len(family_flattened)

    print()
    print(f'- **{i}. Family `{root}` ({n_formulae} {"formulae" if n_formulae > 1 else "formula"})**')
    print()

    if root == 'stand-alones':
        for formula in family_flattened:
            print(f'    - [ ] `{formula}`')
    else:
        print(f'    ```')
        print(f'    {family}')
        print(f'    ```')


# %%
with open('families.json', 'wt') as file:
    json.dump(results, file, indent=2)

@XuehaiPan
Copy link
Contributor Author

CI tap syntax has passed. No more conflict in the dependency trees.

$ git rev-parse HEAD
40171bc81d27ffbd0c53e5503f57365c4d901670

$ for formula in $(find Formula -name 'python@*.rb' | xargs basename -s .rb | sort -rV); do echo "$(grep -rlF 'depends_on "'"${formula}"'"' Formula | wc -l)\tformulae depend on ${formula}"; done
599     formulae depend on python@3.10
103     formulae depend on python@3.9
9       formulae depend on python@3.8
2       formulae depend on python@3.7

@carlocab
Copy link
Member

carlocab commented Aug 7, 2022

Since we're already modifying python@3.9 here, I think we should consider doing make altinstall with it, so that it doesn't need to be keg-only. This makes it so that it can be installed alongside a different version of Python without conflicts.

See https://github.com/python/cpython#installing-multiple-versions.

Implemented this at #107517.

@XuehaiPan
Copy link
Contributor Author

Since we're already modifying python@3.9 here, I think we should consider doing make altinstall with it, so that it doesn't need to be keg-only. This makes it so that it can be installed alongside a different version of Python without conflicts.

Since make altinstall does not make a symlink python3 to python3.x in the prefix, it will breaks dependent formulae that use:

depends_on "python@3.9"

python = Formula["python@3.9"].bin/"python3"

or

depends_on "python@3.9"

python = which("python3")

There are bunch of formulae need to add .9 suffix as Formula["python@3.9"].bin/"python3.9". All of the dependent update should be included in PR #107517. It would be a huge PR with lots of file changes.

If we make a symlink prefix/bin/python3 to prefix/bin/python3.x, it will break brew link.

@carlocab
Copy link
Member

carlocab commented Aug 7, 2022

For those formulae in particular:

There aren't too many of those. I count less than 35. But it is not necessary to fix them all in the same PR.

Some alternatives:

  • fix them to look for python3.9 instead of python3 separately. There already is a python3.9 we can use even without merging my PR. [*]
  • add python@3.9's libexec/"bin" to PATH during the build. This can even be handled in brew.

The second doesn't completely fix the ones that look for python3 at a specific path, but that can be adjusted in a separate PR too.

Of course, there could be many more formulae that assume python@3.9's python3 is in bin, but I'm not too concerned about fixing them all, especially if this is a short-term transitional cost we pay in order to make future Python migrations easier.

[*] This is also useful for the formulae that still have both python@3.9 and python@3.10 in their dep tree, since we're not too sure which formula python3 belongs to when making an unqualified call.

@XuehaiPan
Copy link
Contributor Author

XuehaiPan commented Aug 7, 2022

Require 91 + 38 + 28 separate PRs to add major_minor to the python executable suffix.

$ for formula in $(find Formula -name 'python@*.rb' | xargs basename -s .rb | sort -rV); do echo "$(grep -rlF 'Formula["'"${formula}"'"].opt_bin' Formula | wc -l)\tformulae use python3 in ${formula}'s opt_bin"; done
91      formulae use python3 in python@3.10's opt_bin
38      formulae use python3 in python@3.9's opt_bin
0       formulae use python3 in python@3.8's opt_bin
0       formulae use python3 in python@3.7's opt_bin

$ grep -rlF 'which("python3")' Formula | wc -l
28

It would be good to handle this in brew.

We can also add new attributes to python@3.x formulae:

def python3
    bin/"python#{version.major_minor}"
end

def python
    bin/"python#{version.major_minor}"
end

Then in the dependent:

-python = Formula["python@3.9"].bin/"python3"
+python = Formula["python@3.9"].python3

I think all of these may be out of the scope about migrating alias. Although make altinstall is a great feature, the user may want to have python@3.10 as the default python3 as soon as possible. We can do these changes mentioned above later (If the CI permits, tests for Python are heavy.).

.github/workflows/triage.yml Outdated Show resolved Hide resolved
carlocab added a commit to carlocab/homebrew-core that referenced this pull request Aug 11, 2022
This makes sure that Python does not install major-versioned files (e.g.
`python3`, `python3-config`) and instead only installs files with both a
major and minor version (e.g. `python3.9`, `python3.9-config`).

This will allow us to have multiple versions of Python linked into
`HOMEBREW_PREFIX` without conflicts.

If users want to call `python3.9` as `python3`, then they can add
`libexec/"bin"` to their `PATH`.

Going forward, this will help simplify migrating to newer versions of
Python, since we will not need to make the newer version keg-only while
dependent formulae are migrated.

See discussion at Homebrew/discussions#2776, and documentation at

    https://github.com/python/cpython#installing-multiple-versions

Closes Homebrew#87075.
Homebrew TODO automation moved this from Pending Review to Done Aug 12, 2022
@carlocab
Copy link
Member

Thanks for your help with pushing the migration, @XuehaiPan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge-skip `brew pr-automerge` will skip this pull request in progress Stale bot should stay away long build Needs CI-long-timeout maintainer feedback Additional maintainers' opinions may be needed python-3.10-migration
Projects
Homebrew TODO
  
Done
Development

Successfully merging this pull request may close these issues.

None yet