From 08b30a01e61a8350296d599222d4e5485ebbc811 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 9 Feb 2022 12:42:22 -0500 Subject: [PATCH 01/15] Restore custom `distutils` handling for resolving paths to submodules. --- .github/workflows/ci.yaml | 26 ++++++++++++++++++++++++++ ChangeLog | 4 ++++ astroid/interpreter/_import/spec.py | 12 ++++++++++++ 3 files changed, 42 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 277b0eb543..e13ec6841c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -406,3 +406,29 @@ jobs: run: | . venv/bin/activate pytest tests/ + + extra-special-test-case: + name: Test case that requires specific virtualenv + runs-on: windows-latest + timeout-minutes: 5 + steps: + - name: Check out code from GitHub + uses: actions/checkout@v2.3.4 + with: + fetch-depth: 0 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python + uses: actions/setup-python@v2.2.1 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + - name: Create venv and run test case + run: | + python -m pip install virtualenv==15.1.0 + python -m virtualenv venv2 + . venv2\scripts\activate + which python + python -m pip install pylint + python -m pip install -e . + echo "import distutils.util # pylint: disable=unused-import" > test.py + cat test.py + pylint test.py diff --git a/ChangeLog b/ChangeLog index 6baadce493..85347e14e9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -86,6 +86,10 @@ Release date: 2021-12-31 Ref #1321 +* Restore custom ``distutils`` handling for resolving paths to submodules. + + Closes PyCQA/pylint#5645 + * Fix ``deque.insert()`` signature in ``collections`` brain. Closes #1260 diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index 57bab9f434..ddc24d58eb 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -23,6 +23,8 @@ import sys import zipimport from functools import lru_cache +from importlib.util import find_spec +from pathlib import Path from . import util @@ -160,6 +162,16 @@ def contribute_to_path(self, spec, processed): for p in sys.path if os.path.isdir(os.path.join(p, *processed)) ] + elif spec.name == "distutils": + # virtualenv below 20.0 patches distutils in an unexpected way + # so we just find the location of distutils that will be + # imported to avoid spurious import-error messages + distutils_spec = find_spec("distutils") + if distutils_spec: + origin_path = Path(distutils_spec.origin) # e.g. .../distutils/__init__.py + path = [str(origin_path.parent)] # e.g. .../distutils + else: + path = [spec.location] else: path = [spec.location] return path From 5fa8fbfe7fb79040799a3d00bbdd25bf5a5869ab Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 9 Feb 2022 20:23:27 +0000 Subject: [PATCH 02/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- astroid/interpreter/_import/spec.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index ddc24d58eb..c415d03ed2 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -168,7 +168,9 @@ def contribute_to_path(self, spec, processed): # imported to avoid spurious import-error messages distutils_spec = find_spec("distutils") if distutils_spec: - origin_path = Path(distutils_spec.origin) # e.g. .../distutils/__init__.py + origin_path = Path( + distutils_spec.origin + ) # e.g. .../distutils/__init__.py path = [str(origin_path.parent)] # e.g. .../distutils else: path = [spec.location] From 301f35065770af335a3510e1779f1ffb72f3e5d6 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 9 Feb 2022 15:27:39 -0500 Subject: [PATCH 03/15] no reimport --- astroid/interpreter/_import/spec.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index c415d03ed2..a0f961ec5e 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -23,7 +23,6 @@ import sys import zipimport from functools import lru_cache -from importlib.util import find_spec from pathlib import Path from . import util @@ -166,7 +165,7 @@ def contribute_to_path(self, spec, processed): # virtualenv below 20.0 patches distutils in an unexpected way # so we just find the location of distutils that will be # imported to avoid spurious import-error messages - distutils_spec = find_spec("distutils") + distutils_spec = importlib.util.find_spec("distutils") if distutils_spec: origin_path = Path( distutils_spec.origin From 8d6e0f6be50b1de5ddf907887bba0964192464d5 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 19 Feb 2022 12:04:42 -0500 Subject: [PATCH 04/15] Improve CI step --- .github/workflows/ci.yaml | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e13ec6841c..f3c0fd327b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -407,28 +407,32 @@ jobs: . venv/bin/activate pytest tests/ - extra-special-test-case: - name: Test case that requires specific virtualenv + specific-virtualenv-test: + name: Regression test for virtualenv==15.1.0 on Windows runs-on: windows-latest timeout-minutes: 5 steps: - name: Check out code from GitHub uses: actions/checkout@v2.3.4 + - name: Restore Python virtual environment + id: cache-virtualenv15.1.0 + uses: actions/cache@v2.1.4 with: - fetch-depth: 0 - - name: Set up Python ${{ env.DEFAULT_PYTHON }} - id: python - uses: actions/setup-python@v2.2.1 - with: - python-version: ${{ env.DEFAULT_PYTHON }} - - name: Create venv and run test case + path: venv2 + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + steps.generate-python-key.outputs.key }} + restore-keys: | + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-virtualenv15.1.0-${{ env.CACHE_VERSION }}- + - name: Create Python virtual environment with virtualenv==15.1.0 + if: steps.cache-virtualenv15.1.0.outputs.cache-hit != 'true' run: | python -m pip install virtualenv==15.1.0 python -m virtualenv venv2 . venv2\scripts\activate - which python python -m pip install pylint python -m pip install -e . + - name: Test distutils edge case + run: | echo "import distutils.util # pylint: disable=unused-import" > test.py - cat test.py pylint test.py From 14d2e12a74d8779439265c62df7959365ab3ab83 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 19 Feb 2022 12:06:51 -0500 Subject: [PATCH 05/15] Fix cache identifiers --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f3c0fd327b..8adfb4d38f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -415,7 +415,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2.3.4 - name: Restore Python virtual environment - id: cache-virtualenv15.1.0 + id: cache-virtualenv15_1_0 uses: actions/cache@v2.1.4 with: path: venv2 @@ -423,9 +423,9 @@ jobs: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ steps.generate-python-key.outputs.key }} restore-keys: | - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-virtualenv15.1.0-${{ env.CACHE_VERSION }}- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-virtualenv15_1_0-${{ env.CACHE_VERSION }}- - name: Create Python virtual environment with virtualenv==15.1.0 - if: steps.cache-virtualenv15.1.0.outputs.cache-hit != 'true' + if: steps.cache-virtualenv15_1_0.outputs.cache-hit != 'true' run: | python -m pip install virtualenv==15.1.0 python -m virtualenv venv2 From 789f606cbcaf295d9f09c99a2f778cf70c544426 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 19 Feb 2022 12:25:37 -0500 Subject: [PATCH 06/15] python -m pylint --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8adfb4d38f..aaec992ade 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -435,4 +435,4 @@ jobs: - name: Test distutils edge case run: | echo "import distutils.util # pylint: disable=unused-import" > test.py - pylint test.py + python -m pylint test.py From 9e374faf1b3bfb4ab5ab0eda996bbeb5d2b1d605 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 19 Feb 2022 12:29:04 -0500 Subject: [PATCH 07/15] Activate virtualenv --- .github/workflows/ci.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index aaec992ade..00c64b8ced 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -434,5 +434,6 @@ jobs: python -m pip install -e . - name: Test distutils edge case run: | + . venv2\scripts\activate echo "import distutils.util # pylint: disable=unused-import" > test.py - python -m pylint test.py + pylint test.py From 3a601fab69231a2cf0d27033c02dafcf7fe85076 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 19 Feb 2022 12:35:41 -0500 Subject: [PATCH 08/15] setup python --- .github/workflows/ci.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 00c64b8ced..175fa046c2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -414,6 +414,11 @@ jobs: steps: - name: Check out code from GitHub uses: actions/checkout@v2.3.4 + - name: Set up Python ${{ matrix.python-version }} + id: python + uses: actions/setup-python@v2.2.1 + with: + python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment id: cache-virtualenv15_1_0 uses: actions/cache@v2.1.4 From de390f55d32b612d44e3605bae487b1aa763c68e Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 19 Feb 2022 12:38:28 -0500 Subject: [PATCH 09/15] better cache key --- .github/workflows/ci.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 175fa046c2..4a3d6f9690 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -420,15 +420,14 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Restore Python virtual environment - id: cache-virtualenv15_1_0 + id: cache-virtualenv15 uses: actions/cache@v2.1.4 with: path: venv2 key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - steps.generate-python-key.outputs.key }} + ${{ runner.os }}-virtualenv15 restore-keys: | - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-virtualenv15_1_0-${{ env.CACHE_VERSION }}- + ${{ runner.os }}-virtualenv15-${{ env.CACHE_VERSION }} - name: Create Python virtual environment with virtualenv==15.1.0 if: steps.cache-virtualenv15_1_0.outputs.cache-hit != 'true' run: | From ed6c8f17dac0d84c336cbd17dca4f6b082b7b059 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 21 Feb 2022 08:05:54 -0500 Subject: [PATCH 10/15] Update .github/workflows/ci.yaml Co-authored-by: Pierre Sassoulas --- .github/workflows/ci.yaml | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4a3d6f9690..277b0eb543 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -406,38 +406,3 @@ jobs: run: | . venv/bin/activate pytest tests/ - - specific-virtualenv-test: - name: Regression test for virtualenv==15.1.0 on Windows - runs-on: windows-latest - timeout-minutes: 5 - steps: - - name: Check out code from GitHub - uses: actions/checkout@v2.3.4 - - name: Set up Python ${{ matrix.python-version }} - id: python - uses: actions/setup-python@v2.2.1 - with: - python-version: ${{ matrix.python-version }} - - name: Restore Python virtual environment - id: cache-virtualenv15 - uses: actions/cache@v2.1.4 - with: - path: venv2 - key: >- - ${{ runner.os }}-virtualenv15 - restore-keys: | - ${{ runner.os }}-virtualenv15-${{ env.CACHE_VERSION }} - - name: Create Python virtual environment with virtualenv==15.1.0 - if: steps.cache-virtualenv15_1_0.outputs.cache-hit != 'true' - run: | - python -m pip install virtualenv==15.1.0 - python -m virtualenv venv2 - . venv2\scripts\activate - python -m pip install pylint - python -m pip install -e . - - name: Test distutils edge case - run: | - . venv2\scripts\activate - echo "import distutils.util # pylint: disable=unused-import" > test.py - pylint test.py From d95fe1a92d7a0f35eb5c2816d28acc8747bff9b8 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 22 Feb 2022 08:35:13 -0500 Subject: [PATCH 11/15] Revert "Update .github/workflows/ci.yaml" This reverts commit ed6c8f17dac0d84c336cbd17dca4f6b082b7b059. --- .github/workflows/ci.yaml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 277b0eb543..4a3d6f9690 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -406,3 +406,38 @@ jobs: run: | . venv/bin/activate pytest tests/ + + specific-virtualenv-test: + name: Regression test for virtualenv==15.1.0 on Windows + runs-on: windows-latest + timeout-minutes: 5 + steps: + - name: Check out code from GitHub + uses: actions/checkout@v2.3.4 + - name: Set up Python ${{ matrix.python-version }} + id: python + uses: actions/setup-python@v2.2.1 + with: + python-version: ${{ matrix.python-version }} + - name: Restore Python virtual environment + id: cache-virtualenv15 + uses: actions/cache@v2.1.4 + with: + path: venv2 + key: >- + ${{ runner.os }}-virtualenv15 + restore-keys: | + ${{ runner.os }}-virtualenv15-${{ env.CACHE_VERSION }} + - name: Create Python virtual environment with virtualenv==15.1.0 + if: steps.cache-virtualenv15_1_0.outputs.cache-hit != 'true' + run: | + python -m pip install virtualenv==15.1.0 + python -m virtualenv venv2 + . venv2\scripts\activate + python -m pip install pylint + python -m pip install -e . + - name: Test distutils edge case + run: | + . venv2\scripts\activate + echo "import distutils.util # pylint: disable=unused-import" > test.py + pylint test.py From 7abdd57f55ada0e50ba9db0e62fa64072fac9453 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 26 Feb 2022 17:51:45 -0500 Subject: [PATCH 12/15] Move virtualenv 15.0 test to "release test" workflow --- .github/workflows/ci.yaml | 35 ----------------------------- .github/workflows/release-tests.yml | 32 ++++++++++++++++++++++++++ doc/release.md | 2 ++ 3 files changed, 34 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/release-tests.yml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4a3d6f9690..277b0eb543 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -406,38 +406,3 @@ jobs: run: | . venv/bin/activate pytest tests/ - - specific-virtualenv-test: - name: Regression test for virtualenv==15.1.0 on Windows - runs-on: windows-latest - timeout-minutes: 5 - steps: - - name: Check out code from GitHub - uses: actions/checkout@v2.3.4 - - name: Set up Python ${{ matrix.python-version }} - id: python - uses: actions/setup-python@v2.2.1 - with: - python-version: ${{ matrix.python-version }} - - name: Restore Python virtual environment - id: cache-virtualenv15 - uses: actions/cache@v2.1.4 - with: - path: venv2 - key: >- - ${{ runner.os }}-virtualenv15 - restore-keys: | - ${{ runner.os }}-virtualenv15-${{ env.CACHE_VERSION }} - - name: Create Python virtual environment with virtualenv==15.1.0 - if: steps.cache-virtualenv15_1_0.outputs.cache-hit != 'true' - run: | - python -m pip install virtualenv==15.1.0 - python -m virtualenv venv2 - . venv2\scripts\activate - python -m pip install pylint - python -m pip install -e . - - name: Test distutils edge case - run: | - . venv2\scripts\activate - echo "import distutils.util # pylint: disable=unused-import" > test.py - pylint test.py diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml new file mode 100644 index 0000000000..05ecbd08a6 --- /dev/null +++ b/.github/workflows/release-tests.yml @@ -0,0 +1,32 @@ +name: Release tests + +on: workflow_dispatch + +env: + DEFAULT_PYTHON: 3.8 + +jobs: + virtualenv-15-windows-test: + name: Regression test for virtualenv==15.1.0 on Windows + runs-on: windows-latest + timeout-minutes: 5 + steps: + - name: Check out code from GitHub + uses: actions/checkout@v2.3.4 + - name: Set up Python + id: python + uses: actions/setup-python@v2.2.1 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + - name: Create Python virtual environment with virtualenv==15.1.0 + run: | + python -m pip install virtualenv==15.1.0 + python -m virtualenv venv2 + . venv2\scripts\activate + python -m pip install pylint + python -m pip install -e . + - name: Test no import-error from distutils.util + run: | + . venv2\scripts\activate + echo "import distutils.util # pylint: disable=unused-import" > test.py + pylint test.py diff --git a/doc/release.md b/doc/release.md index 96855ef3ee..1646e42fc3 100644 --- a/doc/release.md +++ b/doc/release.md @@ -4,6 +4,8 @@ So, you want to release the `X.Y.Z` version of astroid ? ## Process +(Consider triggering the "release tests" workflow in GitHub Actions first.) + 1. Check if the dependencies of the package are correct 2. Check the result (Do `git diff vX.Y.Z-1 ChangeLog` in particular). 3. Install the release dependencies `pip3 install pre-commit tbump` From b0134dc65c2f7d4458727d86f05f92786f5402cf Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 26 Feb 2022 18:00:12 -0500 Subject: [PATCH 13/15] add comment re: github actions --- astroid/interpreter/_import/spec.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index a0f961ec5e..7c6d0e3323 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -165,6 +165,8 @@ def contribute_to_path(self, spec, processed): # virtualenv below 20.0 patches distutils in an unexpected way # so we just find the location of distutils that will be # imported to avoid spurious import-error messages + # A regression test to create this scenario exists in release-tests.yml + # and can be triggered manually from GitHub Actions distutils_spec = importlib.util.find_spec("distutils") if distutils_spec: origin_path = Path( From 17388bd8e0381fbc5268bb4728ec6e0c8cb5549b Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 26 Feb 2022 19:34:34 -0500 Subject: [PATCH 14/15] Further comments --- .github/workflows/release-tests.yml | 1 + astroid/interpreter/_import/spec.py | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml index 05ecbd08a6..3aa46d9246 100644 --- a/.github/workflows/release-tests.yml +++ b/.github/workflows/release-tests.yml @@ -6,6 +6,7 @@ env: DEFAULT_PYTHON: 3.8 jobs: + # Regression test added in https://github.com/PyCQA/astroid/pull/1386 virtualenv-15-windows-test: name: Regression test for virtualenv==15.1.0 on Windows runs-on: windows-latest diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index 7c6d0e3323..f6c5726619 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -165,6 +165,7 @@ def contribute_to_path(self, spec, processed): # virtualenv below 20.0 patches distutils in an unexpected way # so we just find the location of distutils that will be # imported to avoid spurious import-error messages + # https://github.com/PyCQA/pylint/issues/5645 # A regression test to create this scenario exists in release-tests.yml # and can be triggered manually from GitHub Actions distutils_spec = importlib.util.find_spec("distutils") From 09345aa97e4fb40adf003b12a56a3fda0107c5ed Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 27 Feb 2022 14:47:15 +0100 Subject: [PATCH 15/15] Last changes --- .github/workflows/release-tests.yml | 2 +- astroid/interpreter/_import/spec.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml index 3aa46d9246..dc228ac7c2 100644 --- a/.github/workflows/release-tests.yml +++ b/.github/workflows/release-tests.yml @@ -6,8 +6,8 @@ env: DEFAULT_PYTHON: 3.8 jobs: - # Regression test added in https://github.com/PyCQA/astroid/pull/1386 virtualenv-15-windows-test: + # Regression test added in https://github.com/PyCQA/astroid/pull/1386 name: Regression test for virtualenv==15.1.0 on Windows runs-on: windows-latest timeout-minutes: 5 diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py index f6c5726619..53228bd815 100644 --- a/astroid/interpreter/_import/spec.py +++ b/astroid/interpreter/_import/spec.py @@ -19,6 +19,7 @@ import collections import enum import importlib.machinery +import importlib.util import os import sys import zipimport @@ -169,7 +170,7 @@ def contribute_to_path(self, spec, processed): # A regression test to create this scenario exists in release-tests.yml # and can be triggered manually from GitHub Actions distutils_spec = importlib.util.find_spec("distutils") - if distutils_spec: + if distutils_spec and distutils_spec.origin: origin_path = Path( distutils_spec.origin ) # e.g. .../distutils/__init__.py