commit:     ad3994394af0bc975ec7c28bd60de496b580c25e
Author:     James Le Cuirot <chewi <AT> gentoo <DOT> org>
AuthorDate: Sat Jul 15 11:20:27 2023 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Wed Aug  2 06:31:19 2023 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=ad399439

Migrate from setuptools to Meson and meson-python

This makes Portage PEP 517 compliant.

When building via meson-python, the man pages and logrotate config are
no longer included as there seems little point.

Bug: https://bugs.gentoo.org/910035
Signed-off-by: James Le Cuirot <chewi <AT> gentoo.org>
Signed-off-by: Sam James <sam <AT> gentoo.org>

 .builds/ci.yml                                     |  22 +-
 .builds/lint.yml                                   |  10 +-
 .github/workflows/ci.yml                           |  21 +-
 .github/workflows/lint.yml                         |  11 +-
 DEVELOPING                                         |  13 +-
 MANIFEST.in                                        |  30 -
 NEWS                                               |   2 +
 bin/meson.build                                    |  73 ++
 cnf/meson.build                                    | 144 ++++
 doc/api/meson.build                                |  43 +
 doc/fragment/meson.build                           |   5 +
 doc/fragment/version.in                            |   1 +
 doc/meson.build                                    |  57 ++
 doc/portage.docbook                                |   2 -
 lib/_emerge/AbstractEbuildProcess.py               |   7 +-
 lib/_emerge/create_depgraph_params.py              |  10 +-
 lib/_emerge/meson.build                            | 101 +++
 lib/_emerge/resolver/meson.build                   |  14 +
 lib/meson.build                                    |   2 +
 lib/portage/__init__.py                            |  11 +-
 lib/portage/_compat_upgrade/meson.build            |  10 +
 lib/portage/_emirrordist/meson.build               |  15 +
 lib/portage/_sets/meson.build                      |  15 +
 lib/portage/binrepo/meson.build                    |   8 +
 lib/portage/cache/index/meson.build                |   9 +
 lib/portage/cache/meson.build                      |  20 +
 lib/portage/const.py                               |  61 +-
 lib/portage/dbapi/meson.build                      |  22 +
 lib/portage/dep/meson.build                        |  12 +
 lib/portage/dep/soname/meson.build                 |  10 +
 lib/portage/elog/meson.build                       |  16 +
 lib/portage/emaint/meson.build                     |  11 +
 lib/portage/emaint/modules/binhost/meson.build     |   8 +
 lib/portage/emaint/modules/config/meson.build      |   8 +
 lib/portage/emaint/modules/logs/meson.build        |   8 +
 lib/portage/emaint/modules/merges/meson.build      |   8 +
 lib/portage/emaint/modules/meson.build             |  16 +
 lib/portage/emaint/modules/move/meson.build        |   8 +
 lib/portage/emaint/modules/resume/meson.build      |   8 +
 lib/portage/emaint/modules/sync/meson.build        |   8 +
 lib/portage/emaint/modules/world/meson.build       |   8 +
 lib/portage/env/meson.build                        |  10 +
 lib/portage/installation.py                        |  21 +
 lib/portage/meson.build                            |  74 ++
 lib/portage/package/ebuild/_config/meson.build     |  17 +
 lib/portage/package/ebuild/_ipc/meson.build        |  10 +
 .../package/ebuild/_parallel_manifest/meson.build  |  10 +
 lib/portage/package/ebuild/meson.build             |  23 +
 lib/portage/package/meson.build                    |   9 +
 lib/portage/proxy/meson.build                      |   9 +
 lib/portage/repository/meson.build                 |  10 +
 lib/portage/repository/storage/meson.build         |  11 +
 lib/portage/sync/meson.build                       |  14 +
 lib/portage/sync/modules/cvs/meson.build           |   8 +
 lib/portage/sync/modules/git/meson.build           |   8 +
 lib/portage/sync/modules/mercurial/meson.build     |   8 +
 lib/portage/sync/modules/meson.build               |  14 +
 lib/portage/sync/modules/rsync/meson.build         |   8 +
 lib/portage/sync/modules/svn/meson.build           |   8 +
 lib/portage/sync/modules/webrsync/meson.build      |   8 +
 lib/portage/tests/bin/meson.build                  |  14 +
 lib/portage/tests/conftest.py                      |   7 +-
 lib/portage/tests/dbapi/meson.build                |  12 +
 lib/portage/tests/dep/meson.build                  |  28 +
 lib/portage/tests/ebuild/meson.build               |  17 +
 lib/portage/tests/emerge/meson.build               |  14 +
 lib/portage/tests/env/config/meson.build           |  12 +
 lib/portage/tests/env/meson.build                  |  10 +
 lib/portage/tests/glsa/meson.build                 |   9 +
 lib/portage/tests/gpkg/meson.build                 |  15 +
 lib/portage/tests/lafilefixer/meson.build          |   9 +
 lib/portage/tests/lazyimport/meson.build           |  10 +
 .../test_lazy_import_portage_baseline.py           |   1 +
 lib/portage/tests/lint/meson.build                 |  12 +
 lib/portage/tests/locks/meson.build                |  10 +
 lib/portage/tests/meson.build                      |  32 +
 lib/portage/tests/news/meson.build                 |   9 +
 lib/portage/tests/process/meson.build              |  16 +
 .../resolver/binpkg_multi_instance/meson.build     |  10 +
 lib/portage/tests/resolver/meson.build             |  96 +++
 lib/portage/tests/resolver/soname/meson.build      |  19 +
 lib/portage/tests/runTests.py                      |   7 +-
 lib/portage/tests/sets/base/meson.build            |  10 +
 lib/portage/tests/sets/files/meson.build           |  10 +
 lib/portage/tests/sets/meson.build                 |  12 +
 lib/portage/tests/sets/shell/meson.build           |   9 +
 lib/portage/tests/sync/meson.build                 |   9 +
 lib/portage/tests/unicode/meson.build              |   9 +
 lib/portage/tests/update/meson.build               |  11 +
 lib/portage/tests/util/dyn_libs/meson.build        |   9 +
 lib/portage/tests/util/eventloop/meson.build       |   9 +
 lib/portage/tests/util/file_copy/meson.build       |   9 +
 lib/portage/tests/util/futures/asyncio/meson.build |  15 +
 lib/portage/tests/util/futures/meson.build         |  15 +
 lib/portage/tests/util/meson.build                 |  31 +
 lib/portage/tests/versions/meson.build             |  10 +
 lib/portage/tests/xpak/meson.build                 |   9 +
 lib/portage/util/_async/meson.build                |  20 +
 lib/portage/util/_dyn_libs/meson.build             |  14 +
 lib/portage/util/_eventloop/meson.build            |   9 +
 lib/portage/util/elf/meson.build                   |   9 +
 lib/portage/util/endian/meson.build                |   8 +
 lib/portage/util/file_copy/meson.build             |   7 +
 lib/portage/util/futures/_asyncio/meson.build      |   8 +
 lib/portage/util/futures/executor/meson.build      |   8 +
 lib/portage/util/futures/meson.build               |  17 +
 lib/portage/util/iterators/meson.build             |   8 +
 lib/portage/util/meson.build                       |  49 ++
 lib/portage/xml/meson.build                        |   8 +
 man/color.map.5                                    |   2 +-
 man/dispatch-conf.1                                |   2 +-
 man/ebuild.1                                       |   2 +-
 man/ebuild.5                                       |   2 +-
 man/egencache.1                                    |   2 +-
 man/emaint.1                                       |   2 +-
 man/emerge.1                                       |   2 +-
 man/emirrordist.1                                  |   2 +-
 man/env-update.1                                   |   2 +-
 man/etc-update.1                                   |   2 +-
 man/fixpackages.1                                  |   2 +-
 man/glsa-check.1                                   |   2 +-
 man/make.conf.5                                    |   2 +-
 man/meson.build                                    |  31 +
 man/portage.5                                      |   2 +-
 man/quickpkg.1                                     |   2 +-
 man/ru/color.map.5                                 |   2 +-
 man/ru/dispatch-conf.1                             |   2 +-
 man/ru/ebuild.1                                    |   2 +-
 man/ru/env-update.1                                |   2 +-
 man/ru/etc-update.1                                |   2 +-
 man/ru/fixpackages.1                               |   2 +-
 man/ru/meson.build                                 |  19 +
 meson.build                                        | 123 +++
 meson_options.txt                                  |  59 ++
 pyproject.toml                                     |  37 +-
 setup.py                                           | 925 ---------------------
 src/meson.build                                    |  50 ++
 tox.ini                                            |   2 +-
 138 files changed, 2087 insertions(+), 1060 deletions(-)

diff --git a/.builds/ci.yml b/.builds/ci.yml
index 797aea074..2c1659c84 100644
--- a/.builds/ci.yml
+++ b/.builds/ci.yml
@@ -9,8 +9,6 @@ repositories:
   # pypy: https://ppa.launchpadcontent.net/pypy/ppa/ubuntu jammy main 
"251104D968854915"
 environment:
   PYTHON_VERSIONS:
-    - '3.7'
-    - '3.8'
     - '3.9'
     - '3.10'
     - '3.11'
@@ -21,26 +19,20 @@ tasks:
       portage/.builds/setup-python.sh "${PYTHON_VERSIONS[@]}"
 
   - setup-tests: |
+      sudo apt-get install -y --no-install-recommends meson
       for py in "${PYTHON_VERSIONS[@]}"; do
-        source ".venv-$py/bin/activate"
-        pip install pytest
-        deactivate
+        # setuptools needed for 3.12+ because of 
https://github.com/mesonbuild/meson/issues/7702.
+        ".venv-$py/bin/pip" install pytest setuptools
       done
 
   - test-install: |
       for py in "${PYTHON_VERSIONS[@]}"; do
-        source ".venv-$py/bin/activate"
-        pushd portage
-          time ./setup.py clean install
-        popd
-        deactivate
+        echo -e "[binaries]\npython = '${PWD}/.venv-${py}/bin/python'" > 
/tmp/native.ini
+        meson setup --native-file /tmp/native.ini "/tmp/build-$py" portage
+        meson install -C "/tmp/build-$py"
       done
 
   - test-portage: |
       for py in "${PYTHON_VERSIONS[@]}"; do
-        source ".venv-$py/bin/activate"
-        pushd portage
-          ./setup.py clean test
-        popd
-        deactivate
+        meson test -C "/tmp/build-$py" --verbose
       done

diff --git a/.builds/lint.yml b/.builds/lint.yml
index 2301c87be..847552aac 100644
--- a/.builds/lint.yml
+++ b/.builds/lint.yml
@@ -7,8 +7,6 @@ repositories:
   deadsnakes: https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu jammy 
main "BA6932366A755776"
 environment:
   PYTHON_VERSIONS:
-    - '3.7'
-    - '3.8'
     - '3.9'
     - '3.10'
     - '3.11'
@@ -17,15 +15,11 @@ tasks:
       portage/.builds/setup-python.sh "${PYTHON_VERSIONS[@]}"
 
   - setup-black: |
-      source .venv/bin/activate
-      pip install black
-      deactivate
+      .venv/bin/pip install black
 
   - setup-pylint: |
       for py in "${PYTHON_VERSIONS[@]}"; do
-        source ".venv-$py/bin/activate"
-        pip install pylint pytest
-        deactivate
+        ".venv-$py/bin/pip" install pylint pytest
       done
 
   - black: |

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6bfe4847a..519bf92c4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -24,20 +24,21 @@ jobs:
         uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
-      - name: Install python dependencies
+      - name: Install dependencies
         run: |
           set -xe
           sudo apt-get update
-          sudo apt-get install -y --no-install-recommends libxslt-dev 
libxml2-dev libxml2-utils zstd
+          sudo apt-get install -y --no-install-recommends libxslt-dev 
libxml2-dev libxml2-utils meson zstd
           python -VV
           python -m site
           python -m pip install --upgrade pip
-          python -m pip install tox tox-gh-actions setuptools
-      - name: Test ./setup.py install --root=/tmp/install-root
+          # setuptools needed for 3.12+ because of 
https://github.com/mesonbuild/meson/issues/7702.
+          python -m pip install pytest setuptools
+      - name: Test meson install --destdir /tmp/install-root
         run: |
-          printf "[build_ext]\nportage_ext_modules=true" >> setup.cfg
-          ./setup.py install --root=/tmp/install-root
-      - name: Run tox test env for ${{ matrix.python-version }}
-        run: tox -vv
-        env:
-          TARGET: test
+          echo -e "[binaries]\npython = '$(command -v python)'" > 
/tmp/native.ini
+          meson setup --native-file /tmp/native.ini /tmp/build .
+          meson install -C /tmp/build --destdir /tmp/install-root
+      - name: Run tests for ${{ matrix.python-version }}
+        run: |
+          meson test -C /tmp/build --verbose

diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 469cffe37..f2af40957 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -25,7 +25,7 @@ jobs:
           - '3.9'
           - '3.10'
           - '3.11'
-          # Complains about importing distutils.dir_utils
+          # pylint currently broken under 3.12
           # - '3.12-dev'
       fail-fast: false
     steps:
@@ -40,8 +40,7 @@ jobs:
           python -VV
           python -m site
           python -m pip install --upgrade pip
-          python -m pip install tox tox-gh-actions
-      - name: Run tox pylint env for ${{ matrix.python-version }}
-        run: tox -vv
-        env:
-          TARGET: pylint
+          python -m pip install pylint pytest
+      - name: Run pylint for ${{ matrix.python-version }}
+        run: |
+          ./run-pylint

diff --git a/DEVELOPING b/DEVELOPING
index 0d414c4da..a49afff83 100644
--- a/DEVELOPING
+++ b/DEVELOPING
@@ -213,14 +213,17 @@ Releases
 
 1. Repository mangling:
        - Update NEWS (for both new version & release date)
-       - Update setup.py
+       - Update meson.build
 and commit.
 
 2. Create a git tag for this release:
        git tag -a -s portage-3.0.30
 
-3. Create the tarball and run the tests: ./runtests
-   (see PYTHON_SUPPORTED_VERSIONS in runtests).
+3. Create the tarball and run the tests:
+     - meson setup -Dmodules-only=true build
+     - meson test -C build --verbose
+   Use meson setup's --native-file to override the Python version. See
+   PYTHON_SUPPORTED_VERSIONS in runtests.
 
 4. Version bump the ebuild locally (don't push) and verify it can re-install 
itself:
        emerge --oneshot sys-apps/portage
@@ -234,8 +237,8 @@ and commit.
 6. Create the release for pypi and upload it there:
        - python -m venv .venv
        - . .venv/bin/activate
-       - pip install wheel twine
-       - python setup.py bdist_wheel sdist
+       - pip install build twine
+       - python -m build
        - twine upload dist/<filenames>
 
 7. Bugzilla wrangling:

diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 31ca9c166..000000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,30 +0,0 @@
-include setup.py
-
-# docs
-include DEVELOPING
-include LICENSE
-include TEST-NOTES
-
-# docbook sources
-include doc/custom.xsl
-recursive-include doc *.docbook
-
-# sphinx sources
-include doc/api/index.rst
-include doc/api/conf.py
-include doc/api/Makefile
-
-# extra conf files used in ebuild
-include cnf/make.conf.example.*
-
-# extra files for tests
-include .portage_not_installed
-
-# extra scripts
-include misc/*
-
-# extensions
-include src/*
-
-# GPG test keys
-recursive-include lib/portage/tests/.gnupg *

diff --git a/NEWS b/NEWS
index 82e4c8780..1f34bdd90 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ portage-3.0.50 (UNRELEASED)
 
 Breaking changes:
 * The minimum supported Python version is now >= Python 3.9.
+* Portage now installed with Meson and Python sdist + wheel now prepared with
+  meson-python for PEP 517 compliance.
 
 Bug fixes:
 * install-qa-check.d/05prefix: Fix prefixifying shebang for >= EAPI 7 ebuilds

diff --git a/bin/meson.build b/bin/meson.build
new file mode 100644
index 000000000..b076eaa39
--- /dev/null
+++ b/bin/meson.build
@@ -0,0 +1,73 @@
+py_bins = [
+    'ebuild',
+    'egencache',
+    'emerge',
+    'emirrordist',
+    'glsa-check',
+    'gpkg-sign',
+    'portageq',
+    'quickpkg'
+]
+
+other_bins = [
+    'emerge-webrsync'
+]
+
+py_sbins = [
+    'archive-conf',
+    'dispatch-conf',
+    'emaint',
+    'env-update',
+    'fixpackages',
+    'regenworld'
+]
+
+etc_update = 'etc-update'
+
+if eprefix != ''
+    etc_update = configure_file(
+        input : etc_update,
+        output : 'etc-update',
+        command : hprefixify,
+        capture : true
+    )
+endif
+
+# It might seem sensible to use py.install_sources() to install the Python
+# scripts, but it's really just a wrapper around install_data that forces the
+# install_dir. Meson 1.2.0 and later also optimize to bytecode, but Gentoo does
+# this in the ebuild.
+
+install_data(
+    py_bins,
+    install_dir : system_wide ? get_option('bindir') : portage_bindir,
+    install_mode : 'rwxr-xr-x',
+)
+
+install_data(
+    other_bins,
+    install_dir : get_option('bindir'),
+    install_mode : 'rwxr-xr-x',
+)
+
+install_data(
+    py_sbins,
+    install_dir : system_wide ? get_option('sbindir') : portage_bindir,
+    install_mode : 'rwxr-xr-x',
+)
+
+install_data(
+    [etc_update],
+    install_dir : system_wide ? get_option('sbindir') : get_option('bindir'),
+    install_mode : 'rwxr-xr-x',
+)
+
+# Note that exclude_files is currently broken in meson-python, but it should
+# work soon. Having duplicates here isn't that bad in the meantime.
+# https://github.com/mesonbuild/meson-python/issues/317
+
+install_subdir(
+    '.',
+    exclude_files : py_bins + other_bins + py_sbins + ['etc-update', 
'meson.build'],
+    install_dir : portage_bindir
+)

diff --git a/cnf/meson.build b/cnf/meson.build
new file mode 100644
index 000000000..00af62085
--- /dev/null
+++ b/cnf/meson.build
@@ -0,0 +1,144 @@
+install_data(
+    [
+        'etc-update.conf',
+        'dispatch-conf.conf'
+    ],
+    install_dir : sysconfdir
+)
+
+extra_features = []
+make_globals = 'make.globals'
+repos_conf = 'repos.conf'
+
+if get_option('gentoo-dev')
+    extra_features += [
+        'ipc-sandbox',
+        'network-sandbox',
+        'strict-keepdir',
+        'warn-on-large-env'
+    ]
+endif
+
+if get_option('xattr') and host_machine.system() == 'linux'
+    extra_features += [
+        'xattr'
+    ]
+endif
+
+if extra_features.length() > 0
+    make_globals = configure_file(
+        input : make_globals,
+        output : 'make.globals#features',
+        command : [sed, '$aFEATURES="${FEATURES} ' + ' '.join(extra_features) 
+ '"', '@INPUT@'],
+        capture : true
+    )
+endif
+
+if not get_option('rsync-verify')
+    repos_conf = configure_file(
+        input : repos_conf,
+        output : 'repos.conf#rsync-verify',
+        command : [sed, '-r', 
's:\\b(sync-rsync-verify-metamanifest|sync-webrsync-verify-signature)(\\s*=\\s*).*:\\1\\2no:',
 '@INPUT@'],
+        capture : true
+    )
+endif
+
+if eprefix != ''
+    make_globals = configure_file(
+        input : make_globals,
+        output : 'make.globals#eprefix',
+        command : hprefixify,
+        capture : true
+    )
+
+    repos_conf = configure_file(
+        input : repos_conf,
+        output : 'repos.conf#eprefix',
+        command : hprefixify,
+        capture : true
+    )
+endif
+
+arch = host_machine.cpu()
+
+arch = {
+    'aarch64' : 'arm64',
+    'loongarch64' : 'loong',
+    'mips64' : 'mips',
+    'parisc' : 'hppa',
+    'riscv32' : 'risc',
+    'riscv64' : 'risc',
+    's390x' : 's390',
+    'sh4' : 'sh',
+    'sparc64' : 'sparc',
+    'x86_64' : 'amd64'
+}.get(arch, arch)
+
+if host_machine.system() == 'freebsd'
+    arch += '-fbsd'
+endif
+
+make_conf_example = 'make.conf.example'
+diff = make_conf_example + '.' + arch + '.diff'
+fs = import('fs')
+
+if fs.exists(diff)
+    patch = find_program('patch', required : true)
+    make_conf_example = configure_file(
+        input : [make_conf_example, diff],
+        output : 'make.conf.example',
+        command : [patch, '-o', '@OUTPUT@', '@INPUT0@', '@INPUT1@']
+    )
+else
+    warning('Portage does not have an arch-specific configuration for this 
arch. Please notify the arch maintainer about this issue. Using the generic 
configuration.')
+endif
+
+# TODO: Use fs.copyfile() when requiring Meson >=0.64.0.
+
+make_globals = configure_file(
+    input : make_globals,
+    output : 'make.globals',
+    copy : true
+)
+
+repos_conf = configure_file(
+    input : repos_conf,
+    output : 'repos.conf',
+    copy : true
+)
+
+# TODO: Use preserve_path option when requiring Meson >=0.64.0.
+
+install_data(
+    [
+        make_conf_example,
+        make_globals,
+        repos_conf,
+    ],
+    install_dir : portage_datadir / 'config'
+)
+
+install_data(
+    [
+        'repo.postsync.d/example'
+    ],
+    install_dir : portage_datadir / 'config' / 'repo.postsync.d'
+)
+
+install_data(
+    [
+        'sets/portage.conf'
+    ],
+    install_dir : portage_datadir / 'config' / 'sets'
+)
+
+if not system_wide
+    subdir_done()
+endif
+
+install_data(
+    [
+        'logrotate.d/elog-save-summary'
+    ],
+    install_dir : sysconfdir / 'logrotate.d'
+)

diff --git a/doc/api/meson.build b/doc/api/meson.build
new file mode 100644
index 000000000..294be6d07
--- /dev/null
+++ b/doc/api/meson.build
@@ -0,0 +1,43 @@
+sphinx_apidoc = find_program('sphinx-apidoc', required : get_option('apidoc'))
+sphinx_build = find_program('sphinx-build', required : get_option('apidoc'))
+
+if not sphinx_apidoc.found() or not sphinx_build.found()
+    subdir_done()
+endif
+
+api_symlinks = custom_target(
+    'symlinks',
+    input: ['conf.py', 'index.rst'],
+    output : ['conf.py', 'index.rst'],
+    command : ['ln', '-srnf', '@INPUT@', '@OUTDIR@/']
+)
+
+api_rst = custom_target(
+    'rst',
+    output : ['portage.rst'],
+    command : [sphinx_apidoc, '-TPef', '-o', '@OUTDIR@', '@SOURCE_ROOT@/lib', 
'@SOURCE_ROOT@/lib/portage/tests'],
+    env : {'SPHINX_APIDOC_OPTIONS' : 
'members,private-members,undoc-members,show-inheritance,ignore-module-all,inherited-members'}
+)
+
+# sphinx-build generates a lot of files, and it would be awkward to keep a list
+# updated. This workaround is slightly hacky as you're probably not supposed to
+# set "output" to a directory. Note that it cannot include a /. We use
+# install_subdir here, unlike in the parent directory, because it can use a
+# different name for the destination and can exclude some files.
+
+custom_target(
+    'html',
+    depends: [api_symlinks, api_rst],
+    output : ['html'],
+    command : [sphinx_build, '-M', 'html', '@OUTDIR@', '@OUTDIR@/html'],
+    build_by_default : get_option('apidoc')
+)
+
+if get_option('apidoc')
+    install_subdir(
+        meson.current_build_dir() / 'html' / 'html',
+        exclude_directories : ['_sources'],
+        strip_directory : true,
+        install_dir : docdir / 'html' / 'api'
+    )
+endif

diff --git a/doc/fragment/meson.build b/doc/fragment/meson.build
new file mode 100644
index 000000000..c5604d3f0
--- /dev/null
+++ b/doc/fragment/meson.build
@@ -0,0 +1,5 @@
+version_doc_fragment = configure_file(
+    input : 'version.in',
+    output : 'version',
+    configuration : conf_data
+)

diff --git a/doc/fragment/version.in b/doc/fragment/version.in
new file mode 100644
index 000000000..2ca11428e
--- /dev/null
+++ b/doc/fragment/version.in
@@ -0,0 +1 @@
+<releaseinfo>@VERSION@</releaseinfo>

diff --git a/doc/meson.build b/doc/meson.build
new file mode 100644
index 000000000..a671d7114
--- /dev/null
+++ b/doc/meson.build
@@ -0,0 +1,57 @@
+subdir('api')
+subdir('fragment')
+
+xmlto = find_program('xmlto', required : get_option('doc'))
+
+if not xmlto.found()
+    subdir_done()
+endif
+
+docbook_src = [
+    version_doc_fragment,
+    'config.docbook',
+    'config/bashrc.docbook',
+    'config/sets.docbook',
+    'custom.xsl',
+    'dependency_resolution.docbook',
+    'dependency_resolution/decision_making.docbook',
+    'dependency_resolution/package_modeling.docbook',
+    'dependency_resolution/task_scheduling.docbook',
+    'package.docbook',
+    'package/ebuild.docbook',
+    'package/ebuild/eapi/0.docbook',
+    'package/ebuild/eapi/1.docbook',
+    'package/ebuild/eapi/2.docbook',
+    'package/ebuild/eapi/3.docbook',
+    'package/ebuild/eapi/4.docbook',
+    'package/ebuild/eapi/4-slot-abi.docbook',
+    'package/ebuild/eapi/5.docbook',
+    'package/ebuild/helper_functions.docbook',
+    'package/ebuild/phases.docbook',
+    'portage.docbook',
+    'qa.docbook'
+]
+
+custom_target(
+    'xhtml-nochunks',
+    depend_files : docbook_src,
+    output : ['portage.html'],
+    command : ['xmlto', '-o', '@OUTDIR@', '--searchpath', '@OUTDIR@/fragment', 
'-m', '@CURRENT_SOURCE_DIR@/custom.xsl', 'xhtml-nochunks', 
'@CURRENT_SOURCE_DIR@/portage.docbook'],
+    install : get_option('doc') and 
get_option('doc-formats').contains('xhtml-nochunks'),
+    install_dir : docdir / 'html'
+)
+
+# xhtml generates a lot of files, and it would be awkward to keep a list
+# updated. This workaround is slightly hacky as you're probably not supposed to
+# set "output" to a directory. Note that it cannot include a /. Another
+# alternative is to use install_subdir, but it's not much better as you still
+# need to set "output" to a directory.
+
+custom_target(
+    'xhtml',
+    depend_files : docbook_src,
+    output : ['html'],
+    command : ['xmlto', '-o', '@OUTDIR@/html', '--searchpath', 
'@OUTDIR@/fragment', '-m', '@CURRENT_SOURCE_DIR@/custom.xsl', 'xhtml', 
'@CURRENT_SOURCE_DIR@/portage.docbook'],
+    install : get_option('doc') and 
get_option('doc-formats').contains('xhtml'),
+    install_dir : docdir
+)

diff --git a/doc/portage.docbook b/doc/portage.docbook
index 77cf7e87e..6e9ad74d6 100644
--- a/doc/portage.docbook
+++ b/doc/portage.docbook
@@ -2,7 +2,6 @@
 <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
        "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"; [
 
-       <!ENTITY date SYSTEM "fragment/date">
        <!ENTITY version SYSTEM "fragment/version">
 
        <!ENTITY project "portage">
@@ -52,7 +51,6 @@
  </authorgroup>
 
  &version;
- &date;
 </bookinfo>
 
 &config;

diff --git a/lib/_emerge/AbstractEbuildProcess.py 
b/lib/_emerge/AbstractEbuildProcess.py
index 97408806c..96d91b5da 100644
--- a/lib/_emerge/AbstractEbuildProcess.py
+++ b/lib/_emerge/AbstractEbuildProcess.py
@@ -1,4 +1,4 @@
-# Copyright 1999-2020 Gentoo Authors
+# Copyright 1999-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 import functools
@@ -10,6 +10,7 @@ from _emerge.EbuildBuildDir import EbuildBuildDir
 from _emerge.EbuildIpcDaemon import EbuildIpcDaemon
 import portage
 from portage.elog import messages as elog_messages
+from portage import installation
 from portage.package.ebuild._ipc.ExitCommand import ExitCommand
 from portage.package.ebuild._ipc.QueryCommand import QueryCommand
 from portage import os
@@ -51,7 +52,9 @@ class AbstractEbuildProcess(SpawnProcess):
 
     # The EbuildIpcDaemon support is well tested, but this variable
     # is left so we can temporarily disable it if any issues arise.
-    _enable_ipc_daemon = True
+    _enable_ipc_daemon = (
+        installation.TYPE == installation.TYPES.SOURCE or "@IPC@" == "True"
+    )
 
     def __init__(self, **kwargs):
         SpawnProcess.__init__(self, **kwargs)

diff --git a/lib/_emerge/create_depgraph_params.py 
b/lib/_emerge/create_depgraph_params.py
index 1bbca5de9..3e000ae91 100644
--- a/lib/_emerge/create_depgraph_params.py
+++ b/lib/_emerge/create_depgraph_params.py
@@ -1,7 +1,8 @@
-# Copyright 1999-2021 Gentoo Authors
+# Copyright 1999-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
 import logging
+from portage import installation
 from portage.util import writemsg_level
 
 
@@ -106,7 +107,12 @@ def create_depgraph_params(myopts, myaction):
 
     myparams["ignore_soname_deps"] = myopts.get("--ignore-soname-deps", "y")
 
-    dynamic_deps = myopts.get("--dynamic-deps", "y") != "n" and "--nodeps" not 
in myopts
+    dyn_deps_def = (
+        "y" if installation.TYPE == installation.TYPES.SOURCE else 
"@DYN_DEPS_DEFAULT@"
+    )
+    dynamic_deps = (
+        myopts.get("--dynamic-deps", dyn_deps_def) != "n" and "--nodeps" not 
in myopts
+    )
     if dynamic_deps:
         myparams["dynamic_deps"] = True
 

diff --git a/lib/_emerge/meson.build b/lib/_emerge/meson.build
new file mode 100644
index 000000000..672a9cbdb
--- /dev/null
+++ b/lib/_emerge/meson.build
@@ -0,0 +1,101 @@
+AbstractEbuildProcess_py = configure_file(
+    input : 'AbstractEbuildProcess.py',
+    output : 'AbstractEbuildProcess.py',
+    configuration : {'IPC' : get_option('ipc')}
+)
+
+create_depgraph_params_py = configure_file(
+    input : 'create_depgraph_params.py',
+    output : 'create_depgraph_params.py',
+    configuration : {'DYN_DEPS_DEFAULT' : get_option('gentoo-dev') ? 'n' : 'y'}
+)
+
+py.install_sources(
+    [
+        'AbstractDepPriority.py',
+        AbstractEbuildProcess_py,
+        'AbstractPollTask.py',
+        'AsynchronousLock.py',
+        'AsynchronousTask.py',
+        'AtomArg.py',
+        'BinpkgEnvExtractor.py',
+        'BinpkgExtractorAsync.py',
+        'BinpkgFetcher.py',
+        'BinpkgPrefetcher.py',
+        'BinpkgVerifier.py',
+        'Binpkg.py',
+        'BlockerCache.py',
+        'BlockerDB.py',
+        'BlockerDepPriority.py',
+        'Blocker.py',
+        'CompositeTask.py',
+        'DepPriorityNormalRange.py',
+        'DepPrioritySatisfiedRange.py',
+        'DepPriority.py',
+        'DependencyArg.py',
+        'Dependency.py',
+        'EbuildBinpkg.py',
+        'EbuildBuildDir.py',
+        'EbuildBuild.py',
+        'EbuildExecuter.py',
+        'EbuildFetcher.py',
+        'EbuildFetchonly.py',
+        'EbuildIpcDaemon.py',
+        'EbuildMerge.py',
+        'EbuildMetadataPhase.py',
+        'EbuildPhase.py',
+        'EbuildProcess.py',
+        'EbuildSpawnProcess.py',
+        'FakeVartree.py',
+        'FifoIpcDaemon.py',
+        'JobStatusDisplay.py',
+        'MergeListItem.py',
+        'MetadataRegen.py',
+        'MiscFunctionsProcess.py',
+        'PackageArg.py',
+        'PackageMerge.py',
+        'PackagePhase.py',
+        'PackageUninstall.py',
+        'PackageVirtualDbapi.py',
+        'Package.py',
+        'PipeReader.py',
+        'PollScheduler.py',
+        'ProgressHandler.py',
+        'RootConfig.py',
+        'Scheduler.py',
+        'SequentialTaskQueue.py',
+        'SetArg.py',
+        'SpawnProcess.py',
+        'SubProcess.py',
+        'TaskSequence.py',
+        'Task.py',
+        'UninstallFailure.py',
+        'UnmergeDepPriority.py',
+        'UseFlagDisplay.py',
+        'UserQuery.py',
+        'actions.py',
+        'chk_updated_cfg_files.py',
+        'clear_caches.py',
+        'countdown.py',
+        create_depgraph_params_py,
+        'create_world_atom.py',
+        'depgraph.py',
+        'emergelog.py',
+        'getloadavg.py',
+        'help.py',
+        'is_valid_package_atom.py',
+        'main.py',
+        'post_emerge.py',
+        'search.py',
+        'show_invalid_depstring_notice.py',
+        'stdout_spinner.py',
+        'unmerge.py',
+        '_find_deep_system_runtime_deps.py',
+        '_flush_elog_mod_echo.py',
+        '__init__.py',
+    ],
+    subdir : '_emerge',
+    pure : false
+)
+
+subdir('resolver')

diff --git a/lib/_emerge/resolver/meson.build b/lib/_emerge/resolver/meson.build
new file mode 100644
index 000000000..65b519433
--- /dev/null
+++ b/lib/_emerge/resolver/meson.build
@@ -0,0 +1,14 @@
+py.install_sources(
+    [
+        'DbapiProvidesIndex.py',
+        'backtracking.py',
+        'circular_dependency.py',
+        'output.py',
+        'output_helpers.py',
+        'package_tracker.py',
+        'slot_collision.py',
+        '__init__.py',
+    ],
+    subdir : '_emerge/resolver',
+    pure : false
+)

diff --git a/lib/meson.build b/lib/meson.build
new file mode 100644
index 000000000..aa1a469ca
--- /dev/null
+++ b/lib/meson.build
@@ -0,0 +1,2 @@
+subdir('portage')
+subdir('_emerge')

diff --git a/lib/portage/__init__.py b/lib/portage/__init__.py
index aa7e69920..847a879cd 100644
--- a/lib/portage/__init__.py
+++ b/lib/portage/__init__.py
@@ -1,13 +1,13 @@
-# Copyright 1998-2021 Gentoo Authors
+# Copyright 1998-2023 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 # pylint: disable=ungrouped-imports
 
-VERSION = "HEAD"
-
 # ===========================================================================
 # START OF IMPORTS -- START OF IMPORTS -- START OF IMPORTS -- START OF IMPORT
 # ===========================================================================
 
+from portage import installation
+
 try:
     import asyncio
     import sys
@@ -716,7 +716,7 @@ def create_trees(
     return trees
 
 
-if VERSION == "HEAD":
+if installation.TYPE == installation.TYPES.SOURCE:
 
     class _LazyVersion(proxy.objectproxy.ObjectProxy):
         def _get_target(self):
@@ -775,6 +775,9 @@ if VERSION == "HEAD":
 
     VERSION = _LazyVersion()
 
+else:
+    VERSION = "@VERSION@"
+
 _legacy_global_var_names = (
     "archlist",
     "db",

diff --git a/lib/portage/_compat_upgrade/meson.build 
b/lib/portage/_compat_upgrade/meson.build
new file mode 100644
index 000000000..178de8692
--- /dev/null
+++ b/lib/portage/_compat_upgrade/meson.build
@@ -0,0 +1,10 @@
+py.install_sources(
+    [
+        'binpkg_compression.py',
+        'binpkg_multi_instance.py',
+        'default_locations.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/_compat_upgrade',
+    pure : false
+)

diff --git a/lib/portage/_emirrordist/meson.build 
b/lib/portage/_emirrordist/meson.build
new file mode 100644
index 000000000..9e097cee8
--- /dev/null
+++ b/lib/portage/_emirrordist/meson.build
@@ -0,0 +1,15 @@
+py.install_sources(
+    [
+        'Config.py',
+        'ContentDB.py',
+        'DeletionIterator.py',
+        'DeletionTask.py',
+        'FetchIterator.py',
+        'FetchTask.py',
+        'MirrorDistTask.py',
+        'main.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/_emirrordist',
+    pure : false
+)

diff --git a/lib/portage/_sets/meson.build b/lib/portage/_sets/meson.build
new file mode 100644
index 000000000..8e0a3aa5f
--- /dev/null
+++ b/lib/portage/_sets/meson.build
@@ -0,0 +1,15 @@
+py.install_sources(
+    [
+        'ProfilePackageSet.py',
+        'base.py',
+        'dbapi.py',
+        'files.py',
+        'libs.py',
+        'profiles.py',
+        'security.py',
+        'shell.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/_sets',
+    pure : false
+)

diff --git a/lib/portage/binrepo/meson.build b/lib/portage/binrepo/meson.build
new file mode 100644
index 000000000..b46c2afa9
--- /dev/null
+++ b/lib/portage/binrepo/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'config.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/binrepo',
+    pure : false
+)

diff --git a/lib/portage/cache/index/meson.build 
b/lib/portage/cache/index/meson.build
new file mode 100644
index 000000000..4f23d7e21
--- /dev/null
+++ b/lib/portage/cache/index/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'IndexStreamIterator.py',
+        'pkg_desc_index.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/cache/index',
+    pure : false
+)

diff --git a/lib/portage/cache/meson.build b/lib/portage/cache/meson.build
new file mode 100644
index 000000000..1fc73f719
--- /dev/null
+++ b/lib/portage/cache/meson.build
@@ -0,0 +1,20 @@
+py.install_sources(
+    [
+        'anydbm.py',
+        'cache_errors.py',
+        'ebuild_xattr.py',
+        'flat_hash.py',
+        'fs_template.py',
+        'mappings.py',
+        'metadata.py',
+        'sqlite.py',
+        'sql_template.py',
+        'template.py',
+        'volatile.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/cache',
+    pure : false
+)
+
+subdir('index')

diff --git a/lib/portage/const.py b/lib/portage/const.py
index f7168d996..bf310bb6e 100644
--- a/lib/portage/const.py
+++ b/lib/portage/const.py
@@ -4,6 +4,8 @@
 
 import os
 
+from portage import installation
+
 # ===========================================================================
 # START OF CONSTANTS -- START OF CONSTANTS -- START OF CONSTANTS -- START OF
 # ===========================================================================
@@ -54,25 +56,53 @@ NEWS_LIB_PATH = "var/lib/gentoo"
 # these variables get EPREFIX prepended automagically when they are
 # translated into their lowercase variants
 DEPCACHE_PATH = f"/{CACHE_PATH}/dep"
-GLOBAL_CONFIG_PATH = "/usr/share/portage/config"
+
+if installation.TYPE == installation.TYPES.MODULE:
+    GLOBAL_CONFIG_PATH = "/share/portage/config"
+else:
+    GLOBAL_CONFIG_PATH = "/usr/share/portage/config"
 
 # these variables are not used with target_root or config_root
 # NOTE: Use realpath(__file__) so that python module symlinks in site-packages
 # are followed back to the real location of the whole portage installation.
-# NOTE: Please keep PORTAGE_BASE_PATH in one line to help substitutions.
-# fmt:off
-PORTAGE_BASE_PATH = os.path.join(os.sep, 
os.sep.join(os.path.realpath(__file__.rstrip("co")).split(os.sep)[:-3]))
-# fmt:on
-PORTAGE_BIN_PATH = f"{PORTAGE_BASE_PATH}/bin"
+if installation.TYPE == installation.TYPES.SYSTEM:
+    PORTAGE_BASE_PATH = """@PORTAGE_BASE_PATH@"""
+elif installation.TYPE == installation.TYPES.MODULE:
+    PORTAGE_BASE_PATH = os.path.join(
+        os.path.realpath(__import__("sys").prefix), "lib/portage"
+    )
+else:
+    PORTAGE_BASE_PATH = os.path.join(
+        os.sep, *os.path.realpath(__file__).split(os.sep)[:-3]
+    )
+
+if installation.TYPE == installation.TYPES.SYSTEM:
+    PORTAGE_BIN_PATH = """@PORTAGE_BIN_PATH@"""
+else:
+    PORTAGE_BIN_PATH = f"{PORTAGE_BASE_PATH}/bin"
+
+# The EPREFIX for the current install is hardcoded here, but access to this
+# constant should be minimal, in favor of access via the EPREFIX setting of
+# a config instance (since it's possible to contruct a config instance with
+# a different EPREFIX). Therefore, the EPREFIX constant should *NOT* be used
+# in the definition of any other constants within this file.
+if installation.TYPE == installation.TYPES.SYSTEM:
+    EPREFIX = BINARY_PREFIX = "@EPREFIX@"
+elif installation.TYPE == installation.TYPES.MODULE:
+    EPREFIX = __import__("sys").prefix
+    BINARY_PREFIX = ""
+else:
+    EPREFIX = BINARY_PREFIX = ""
+
 PORTAGE_PYM_PATH = os.path.realpath(os.path.join(__file__, "../.."))
 LOCALE_DATA_PATH = f"{PORTAGE_BASE_PATH}/locale"  # FIXME: not used
 EBUILD_SH_BINARY = f"{PORTAGE_BIN_PATH}/ebuild.sh"
 MISC_SH_BINARY = f"{PORTAGE_BIN_PATH}/misc-functions.sh"
-SANDBOX_BINARY = "/usr/bin/sandbox"
-FAKEROOT_BINARY = "/usr/bin/fakeroot"
-BASH_BINARY = "/bin/bash"
-MOVE_BINARY = "/bin/mv"
-PRELINK_BINARY = "/usr/sbin/prelink"
+SANDBOX_BINARY = f"{BINARY_PREFIX}/usr/bin/sandbox"
+FAKEROOT_BINARY = f"{BINARY_PREFIX}/usr/bin/fakeroot"
+BASH_BINARY = f"{BINARY_PREFIX}/bin/bash"
+MOVE_BINARY = f"{BINARY_PREFIX}/bin/mv"
+PRELINK_BINARY = f"{BINARY_PREFIX}/usr/sbin/prelink"
 
 INVALID_ENV_FILE = "/etc/spork/is/not/valid/profile.env"
 MERGING_IDENTIFIER = "-MERGING-"
@@ -227,14 +257,7 @@ MANIFEST2_HASH_DEFAULT = "BLAKE2B"
 
 MANIFEST2_IDENTIFIERS = ("AUX", "MISC", "DIST", "EBUILD")
 
-# The EPREFIX for the current install is hardcoded here, but access to this
-# constant should be minimal, in favor of access via the EPREFIX setting of
-# a config instance (since it's possible to contruct a config instance with
-# a different EPREFIX). Therefore, the EPREFIX constant should *NOT* be used
-# in the definition of any other constants within this file.
-EPREFIX = ""
-
-# pick up EPREFIX from the environment if set
+# Redefine EPREFIX from the environment if set
 if "PORTAGE_OVERRIDE_EPREFIX" in os.environ:
     EPREFIX = os.environ["PORTAGE_OVERRIDE_EPREFIX"]
     if EPREFIX:

diff --git a/lib/portage/dbapi/meson.build b/lib/portage/dbapi/meson.build
new file mode 100644
index 000000000..781e29f05
--- /dev/null
+++ b/lib/portage/dbapi/meson.build
@@ -0,0 +1,22 @@
+py.install_sources(
+    [
+        'DummyTree.py',
+        'IndexedPortdb.py',
+        'IndexedVardb.py',
+        'bintree.py',
+        'cpv_expand.py',
+        'dep_expand.py',
+        'porttree.py',
+        'vartree.py',
+        'virtual.py',
+        '_ContentsCaseSensitivityManager.py',
+        '_MergeProcess.py',
+        '_SyncfsProcess.py',
+        '_VdbMetadataDelta.py',
+        '_expand_new_virt.py',
+        '_similar_name_search.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/dbapi',
+    pure : false
+)

diff --git a/lib/portage/dep/meson.build b/lib/portage/dep/meson.build
new file mode 100644
index 000000000..18a605c60
--- /dev/null
+++ b/lib/portage/dep/meson.build
@@ -0,0 +1,12 @@
+py.install_sources(
+    [
+        'dep_check.py',
+        '_dnf.py',
+        '_slot_operator.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/dep',
+    pure : false
+)
+
+subdir('soname')

diff --git a/lib/portage/dep/soname/meson.build 
b/lib/portage/dep/soname/meson.build
new file mode 100644
index 000000000..8f62d24fa
--- /dev/null
+++ b/lib/portage/dep/soname/meson.build
@@ -0,0 +1,10 @@
+py.install_sources(
+    [
+        'SonameAtom.py',
+        'multilib_category.py',
+        'parse.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/dep/soname',
+    pure : false
+)

diff --git a/lib/portage/elog/meson.build b/lib/portage/elog/meson.build
new file mode 100644
index 000000000..4f3fd150c
--- /dev/null
+++ b/lib/portage/elog/meson.build
@@ -0,0 +1,16 @@
+py.install_sources(
+    [
+        'filtering.py',
+        'messages.py',
+        'mod_custom.py',
+        'mod_echo.py',
+        'mod_mail.py',
+        'mod_mail_summary.py',
+        'mod_save.py',
+        'mod_save_summary.py',
+        'mod_syslog.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/elog',
+    pure : false
+)

diff --git a/lib/portage/emaint/meson.build b/lib/portage/emaint/meson.build
new file mode 100644
index 000000000..1700bad49
--- /dev/null
+++ b/lib/portage/emaint/meson.build
@@ -0,0 +1,11 @@
+py.install_sources(
+    [
+        'defaults.py',
+        'main.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/emaint',
+    pure : false
+)
+
+subdir('modules')

diff --git a/lib/portage/emaint/modules/binhost/meson.build 
b/lib/portage/emaint/modules/binhost/meson.build
new file mode 100644
index 000000000..63927d393
--- /dev/null
+++ b/lib/portage/emaint/modules/binhost/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'binhost.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/emaint/modules/binhost',
+    pure : false
+)

diff --git a/lib/portage/emaint/modules/config/meson.build 
b/lib/portage/emaint/modules/config/meson.build
new file mode 100644
index 000000000..d1b6ac950
--- /dev/null
+++ b/lib/portage/emaint/modules/config/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'config.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/emaint/modules/config',
+    pure : false
+)

diff --git a/lib/portage/emaint/modules/logs/meson.build 
b/lib/portage/emaint/modules/logs/meson.build
new file mode 100644
index 000000000..856a5705f
--- /dev/null
+++ b/lib/portage/emaint/modules/logs/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'logs.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/emaint/modules/logs',
+    pure : false
+)

diff --git a/lib/portage/emaint/modules/merges/meson.build 
b/lib/portage/emaint/modules/merges/meson.build
new file mode 100644
index 000000000..3223104b2
--- /dev/null
+++ b/lib/portage/emaint/modules/merges/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'merges.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/emaint/modules/merges',
+    pure : false
+)

diff --git a/lib/portage/emaint/modules/meson.build 
b/lib/portage/emaint/modules/meson.build
new file mode 100644
index 000000000..d92b4af48
--- /dev/null
+++ b/lib/portage/emaint/modules/meson.build
@@ -0,0 +1,16 @@
+py.install_sources(
+    [
+        '__init__.py',
+    ],
+    subdir : 'portage/emaint/modules',
+    pure : false
+)
+
+subdir('binhost')
+subdir('config')
+subdir('logs')
+subdir('merges')
+subdir('move')
+subdir('resume')
+subdir('sync')
+subdir('world')

diff --git a/lib/portage/emaint/modules/move/meson.build 
b/lib/portage/emaint/modules/move/meson.build
new file mode 100644
index 000000000..4f16ddc70
--- /dev/null
+++ b/lib/portage/emaint/modules/move/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'move.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/emaint/modules/move',
+    pure : false
+)

diff --git a/lib/portage/emaint/modules/resume/meson.build 
b/lib/portage/emaint/modules/resume/meson.build
new file mode 100644
index 000000000..e6bf8c12b
--- /dev/null
+++ b/lib/portage/emaint/modules/resume/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'resume.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/emaint/modules/resume',
+    pure : false
+)

diff --git a/lib/portage/emaint/modules/sync/meson.build 
b/lib/portage/emaint/modules/sync/meson.build
new file mode 100644
index 000000000..29617aaef
--- /dev/null
+++ b/lib/portage/emaint/modules/sync/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'sync.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/emaint/modules/sync',
+    pure : false
+)

diff --git a/lib/portage/emaint/modules/world/meson.build 
b/lib/portage/emaint/modules/world/meson.build
new file mode 100644
index 000000000..383457038
--- /dev/null
+++ b/lib/portage/emaint/modules/world/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'world.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/emaint/modules/world',
+    pure : false
+)

diff --git a/lib/portage/env/meson.build b/lib/portage/env/meson.build
new file mode 100644
index 000000000..26c56e34f
--- /dev/null
+++ b/lib/portage/env/meson.build
@@ -0,0 +1,10 @@
+py.install_sources(
+    [
+        'config.py',
+        'loaders.py',
+        'validators.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/env',
+    pure : false
+)

diff --git a/lib/portage/installation.py b/lib/portage/installation.py
new file mode 100644
index 000000000..53396834c
--- /dev/null
+++ b/lib/portage/installation.py
@@ -0,0 +1,21 @@
+# portage: Installation
+# Copyright 2023 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+from enum import Enum
+
+TYPES = Enum(
+    "InstallationType",
+    [
+        "SOURCE",  # Portage is not installed, but running from its source 
tree.
+        "MODULE",  # Portage is installed solely as a Python module.
+        "SYSTEM",  # Portage is fully installed to the system, possibly 
prefixed.
+    ],
+)
+
+if "@INSTALL_TYPE@" == "MODULE":
+    TYPE = TYPES.MODULE
+elif "@INSTALL_TYPE@" == "SYSTEM":
+    TYPE = TYPES.SYSTEM
+else:
+    TYPE = TYPES.SOURCE

diff --git a/lib/portage/meson.build b/lib/portage/meson.build
new file mode 100644
index 000000000..31eda1327
--- /dev/null
+++ b/lib/portage/meson.build
@@ -0,0 +1,74 @@
+const_py = configure_file(
+    input : 'const.py',
+    output : 'const.py',
+    configuration : conf_data
+)
+
+installation_py = configure_file(
+    input : 'installation.py',
+    output : 'installation.py',
+    configuration : conf_data
+)
+
+__init__py = configure_file(
+    input : '__init__.py',
+    output : '__init__.py',
+    configuration : conf_data
+)
+
+py.install_sources(
+    [
+        'binpkg.py',
+        'checksum.py',
+        const_py,
+        'cvstree.py',
+        'data.py',
+        'debug.py',
+        'dispatch_conf.py',
+        'eapi.py',
+        'eclass_cache.py',
+        'exception.py',
+        'getbinpkg.py',
+        'glsa.py',
+        'gpg.py',
+        'gpkg.py',
+        installation_py,
+        'localization.py',
+        'locks.py',
+        'mail.py',
+        'manifest.py',
+        'metadata.py',
+        'module.py',
+        'news.py',
+        'output.py',
+        'process.py',
+        'progress.py',
+        'update.py',
+        'versions.py',
+        'xpak.py',
+        '_global_updates.py',
+        '_legacy_globals.py',
+        '_selinux.py',
+        __init__py,
+    ],
+    subdir : 'portage',
+    pure : false
+)
+
+subdir('binrepo')
+subdir('cache')
+subdir('dbapi')
+subdir('dep')
+subdir('elog')
+subdir('emaint')
+subdir('env')
+subdir('package')
+subdir('proxy')
+subdir('repository')
+subdir('sync')
+subdir('tests')
+subdir('util')
+subdir('xml')
+subdir('_compat_upgrade')
+subdir('_emirrordist')
+subdir('_sets')

diff --git a/lib/portage/package/ebuild/_config/meson.build 
b/lib/portage/package/ebuild/_config/meson.build
new file mode 100644
index 000000000..053542917
--- /dev/null
+++ b/lib/portage/package/ebuild/_config/meson.build
@@ -0,0 +1,17 @@
+py.install_sources(
+    [
+        'KeywordsManager.py',
+        'LicenseManager.py',
+        'LocationsManager.py',
+        'MaskManager.py',
+        'UseManager.py',
+        'VirtualsManager.py',
+        'env_var_validation.py',
+        'features_set.py',
+        'helper.py',
+        'special_env_vars.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/package/ebuild/_config',
+    pure : false
+)

diff --git a/lib/portage/package/ebuild/_ipc/meson.build 
b/lib/portage/package/ebuild/_ipc/meson.build
new file mode 100644
index 000000000..702130c61
--- /dev/null
+++ b/lib/portage/package/ebuild/_ipc/meson.build
@@ -0,0 +1,10 @@
+py.install_sources(
+    [
+        'ExitCommand.py',
+        'IpcCommand.py',
+        'QueryCommand.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/package/ebuild/_ipc',
+    pure : false
+)

diff --git a/lib/portage/package/ebuild/_parallel_manifest/meson.build 
b/lib/portage/package/ebuild/_parallel_manifest/meson.build
new file mode 100644
index 000000000..88aa3b29b
--- /dev/null
+++ b/lib/portage/package/ebuild/_parallel_manifest/meson.build
@@ -0,0 +1,10 @@
+py.install_sources(
+    [
+        'ManifestProcess.py',
+        'ManifestScheduler.py',
+        'ManifestTask.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/package/ebuild/_parallel_manifest',
+    pure : false
+)

diff --git a/lib/portage/package/ebuild/meson.build 
b/lib/portage/package/ebuild/meson.build
new file mode 100644
index 000000000..cf122a911
--- /dev/null
+++ b/lib/portage/package/ebuild/meson.build
@@ -0,0 +1,23 @@
+py.install_sources(
+    [
+        'config.py',
+        'deprecated_profile_check.py',
+        'digestcheck.py',
+        'digestgen.py',
+        'doebuild.py',
+        'fetch.py',
+        'getmaskingreason.py',
+        'getmaskingstatus.py',
+        'prepare_build_dirs.py',
+        'profile_iuse.py',
+        '_metadata_invalid.py',
+        '_spawn_nofetch.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/package/ebuild',
+    pure : false
+)
+
+subdir('_config')
+subdir('_ipc')
+subdir('_parallel_manifest')

diff --git a/lib/portage/package/meson.build b/lib/portage/package/meson.build
new file mode 100644
index 000000000..d3be4f936
--- /dev/null
+++ b/lib/portage/package/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        '__init__.py',
+    ],
+    subdir : 'portage/package',
+    pure : false
+)
+
+subdir('ebuild')

diff --git a/lib/portage/proxy/meson.build b/lib/portage/proxy/meson.build
new file mode 100644
index 000000000..d23c944c4
--- /dev/null
+++ b/lib/portage/proxy/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'lazyimport.py',
+        'objectproxy.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/proxy',
+    pure : false
+)

diff --git a/lib/portage/repository/meson.build 
b/lib/portage/repository/meson.build
new file mode 100644
index 000000000..77b82a526
--- /dev/null
+++ b/lib/portage/repository/meson.build
@@ -0,0 +1,10 @@
+py.install_sources(
+    [
+        'config.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/repository',
+    pure : false
+)
+
+subdir('storage')

diff --git a/lib/portage/repository/storage/meson.build 
b/lib/portage/repository/storage/meson.build
new file mode 100644
index 000000000..b5a5208f8
--- /dev/null
+++ b/lib/portage/repository/storage/meson.build
@@ -0,0 +1,11 @@
+py.install_sources(
+    [
+        'hardlink_quarantine.py',
+        'hardlink_rcu.py',
+        'inplace.py',
+        'interface.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/repository/storage',
+    pure : false
+)

diff --git a/lib/portage/sync/meson.build b/lib/portage/sync/meson.build
new file mode 100644
index 000000000..d2fd9fdc3
--- /dev/null
+++ b/lib/portage/sync/meson.build
@@ -0,0 +1,14 @@
+py.install_sources(
+    [
+        'config_checks.py',
+        'controller.py',
+        'getaddrinfo_validate.py',
+        'old_tree_timestamp.py',
+        'syncbase.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/sync',
+    pure : false
+)
+
+subdir('modules')

diff --git a/lib/portage/sync/modules/cvs/meson.build 
b/lib/portage/sync/modules/cvs/meson.build
new file mode 100644
index 000000000..520d6d810
--- /dev/null
+++ b/lib/portage/sync/modules/cvs/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'cvs.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/sync/modules/cvs',
+    pure : false
+)

diff --git a/lib/portage/sync/modules/git/meson.build 
b/lib/portage/sync/modules/git/meson.build
new file mode 100644
index 000000000..8a32ad375
--- /dev/null
+++ b/lib/portage/sync/modules/git/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'git.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/sync/modules/git',
+    pure : false
+)

diff --git a/lib/portage/sync/modules/mercurial/meson.build 
b/lib/portage/sync/modules/mercurial/meson.build
new file mode 100644
index 000000000..c46f26b6f
--- /dev/null
+++ b/lib/portage/sync/modules/mercurial/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'mercurial.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/sync/modules/mercurial',
+    pure : false
+)

diff --git a/lib/portage/sync/modules/meson.build 
b/lib/portage/sync/modules/meson.build
new file mode 100644
index 000000000..d38a45c9f
--- /dev/null
+++ b/lib/portage/sync/modules/meson.build
@@ -0,0 +1,14 @@
+py.install_sources(
+    [
+        '__init__.py',
+    ],
+    subdir : 'portage/sync/modules',
+    pure : false
+)
+
+subdir('cvs')
+subdir('git')
+subdir('mercurial')
+subdir('rsync')
+subdir('svn')
+subdir('webrsync')

diff --git a/lib/portage/sync/modules/rsync/meson.build 
b/lib/portage/sync/modules/rsync/meson.build
new file mode 100644
index 000000000..49df8135b
--- /dev/null
+++ b/lib/portage/sync/modules/rsync/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'rsync.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/sync/modules/rsync',
+    pure : false
+)

diff --git a/lib/portage/sync/modules/svn/meson.build 
b/lib/portage/sync/modules/svn/meson.build
new file mode 100644
index 000000000..0c5ea2c40
--- /dev/null
+++ b/lib/portage/sync/modules/svn/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'svn.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/sync/modules/svn',
+    pure : false
+)

diff --git a/lib/portage/sync/modules/webrsync/meson.build 
b/lib/portage/sync/modules/webrsync/meson.build
new file mode 100644
index 000000000..047d82d2e
--- /dev/null
+++ b/lib/portage/sync/modules/webrsync/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'webrsync.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/sync/modules/webrsync',
+    pure : false
+)

diff --git a/lib/portage/tests/bin/meson.build 
b/lib/portage/tests/bin/meson.build
new file mode 100644
index 000000000..eb7713049
--- /dev/null
+++ b/lib/portage/tests/bin/meson.build
@@ -0,0 +1,14 @@
+py.install_sources(
+    [
+        'setup_env.py',
+        'test_dobin.py',
+        'test_dodir.py',
+        'test_doins.py',
+        'test_eapi7_ver_funcs.py',
+        'test_filter_bash_env.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/bin',
+    pure : false
+)

diff --git a/lib/portage/tests/conftest.py b/lib/portage/tests/conftest.py
index 1913d1f06..88fc72b15 100644
--- a/lib/portage/tests/conftest.py
+++ b/lib/portage/tests/conftest.py
@@ -11,7 +11,6 @@ import signal
 import tempfile
 import shutil
 import sys
-from distutils.dir_util import copy_tree
 
 import pytest
 
@@ -68,8 +67,10 @@ def prepare_environment():
     # Copy GPG test keys to temporary directory
     gpg_path = tempfile.mkdtemp(prefix="gpg_")
 
-    copy_tree(
-        os.path.join(os.path.dirname(os.path.realpath(__file__)), ".gnupg"), 
gpg_path
+    shutil.copytree(
+        os.path.join(os.path.dirname(os.path.realpath(__file__)), ".gnupg"),
+        gpg_path,
+        dirs_exist_ok=True,
     )
 
     os.chmod(gpg_path, 0o700)

diff --git a/lib/portage/tests/dbapi/meson.build 
b/lib/portage/tests/dbapi/meson.build
new file mode 100644
index 000000000..ddb83ba72
--- /dev/null
+++ b/lib/portage/tests/dbapi/meson.build
@@ -0,0 +1,12 @@
+py.install_sources(
+    [
+        'test_auxdb.py',
+        'test_bintree.py',
+        'test_fakedbapi.py',
+        'test_portdb_cache.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/dbapi',
+    pure : false
+)

diff --git a/lib/portage/tests/dep/meson.build 
b/lib/portage/tests/dep/meson.build
new file mode 100644
index 000000000..f96018917
--- /dev/null
+++ b/lib/portage/tests/dep/meson.build
@@ -0,0 +1,28 @@
+py.install_sources(
+    [
+        'testAtom.py',
+        'testCheckRequiredUse.py',
+        'testExtendedAtomDict.py',
+        'testExtractAffectingUSE.py',
+        'testStandalone.py',
+        'test_best_match_to_list.py',
+        'test_dep_getcpv.py',
+        'test_dep_getrepo.py',
+        'test_dep_getslot.py',
+        'test_dep_getusedeps.py',
+        'test_dnf_convert.py',
+        'test_get_operator.py',
+        'test_get_required_use_flags.py',
+        'test_isjustname.py',
+        'test_isvalidatom.py',
+        'test_match_from_list.py',
+        'test_overlap_dnf.py',
+        'test_paren_reduce.py',
+        'test_soname_atom_pickle.py',
+        'test_use_reduce.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/dep',
+    pure : false
+)

diff --git a/lib/portage/tests/ebuild/meson.build 
b/lib/portage/tests/ebuild/meson.build
new file mode 100644
index 000000000..c19a3181a
--- /dev/null
+++ b/lib/portage/tests/ebuild/meson.build
@@ -0,0 +1,17 @@
+py.install_sources(
+    [
+        'test_array_fromfile_eof.py',
+        'test_config.py',
+        'test_doebuild_fd_pipes.py',
+        'test_doebuild_spawn.py',
+        'test_fetch.py',
+        'test_ipc_daemon.py',
+        'test_shell_quote.py',
+        'test_spawn.py',
+        'test_use_expand_incremental.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/ebuild',
+    pure : false
+)

diff --git a/lib/portage/tests/emerge/meson.build 
b/lib/portage/tests/emerge/meson.build
new file mode 100644
index 000000000..faf6fbb7a
--- /dev/null
+++ b/lib/portage/tests/emerge/meson.build
@@ -0,0 +1,14 @@
+py.install_sources(
+    [
+        'test_actions.py',
+        'test_config_protect.py',
+        'test_emerge_blocker_file_collision.py',
+        'test_emerge_slot_abi.py',
+        'test_global_updates.py',
+        'test_simple.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/emerge',
+    pure : false
+)

diff --git a/lib/portage/tests/env/config/meson.build 
b/lib/portage/tests/env/config/meson.build
new file mode 100644
index 000000000..e661f6bac
--- /dev/null
+++ b/lib/portage/tests/env/config/meson.build
@@ -0,0 +1,12 @@
+py.install_sources(
+    [
+        'test_PackageKeywordsFile.py',
+        'test_PackageMaskFile.py',
+        'test_PackageUseFile.py',
+        'test_PortageModulesFile.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/env/config',
+    pure : false
+)

diff --git a/lib/portage/tests/env/meson.build 
b/lib/portage/tests/env/meson.build
new file mode 100644
index 000000000..c56a6b47c
--- /dev/null
+++ b/lib/portage/tests/env/meson.build
@@ -0,0 +1,10 @@
+py.install_sources(
+    [
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/env',
+    pure : false
+)
+
+subdir('config')

diff --git a/lib/portage/tests/glsa/meson.build 
b/lib/portage/tests/glsa/meson.build
new file mode 100644
index 000000000..4bfdc0873
--- /dev/null
+++ b/lib/portage/tests/glsa/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'test_security_set.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/glsa',
+    pure : false
+)

diff --git a/lib/portage/tests/gpkg/meson.build 
b/lib/portage/tests/gpkg/meson.build
new file mode 100644
index 000000000..f5d981936
--- /dev/null
+++ b/lib/portage/tests/gpkg/meson.build
@@ -0,0 +1,15 @@
+py.install_sources(
+    [
+        'test_gpkg_checksum.py',
+        'test_gpkg_gpg.py',
+        'test_gpkg_metadata_update.py',
+        'test_gpkg_metadata_url.py',
+        'test_gpkg_path.py',
+        'test_gpkg_size.py',
+        'test_gpkg_stream.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/gpkg',
+    pure : false
+)

diff --git a/lib/portage/tests/lafilefixer/meson.build 
b/lib/portage/tests/lafilefixer/meson.build
new file mode 100644
index 000000000..93969d1c9
--- /dev/null
+++ b/lib/portage/tests/lafilefixer/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'test_lafilefixer.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/lafilefixer',
+    pure : false
+)

diff --git a/lib/portage/tests/lazyimport/meson.build 
b/lib/portage/tests/lazyimport/meson.build
new file mode 100644
index 000000000..6d177b9ad
--- /dev/null
+++ b/lib/portage/tests/lazyimport/meson.build
@@ -0,0 +1,10 @@
+py.install_sources(
+    [
+        'test_lazy_import_portage_baseline.py',
+        'test_preload_portage_submodules.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/lazyimport',
+    pure : false
+)

diff --git a/lib/portage/tests/lazyimport/test_lazy_import_portage_baseline.py 
b/lib/portage/tests/lazyimport/test_lazy_import_portage_baseline.py
index a7cd93d4f..cbeba37b5 100644
--- a/lib/portage/tests/lazyimport/test_lazy_import_portage_baseline.py
+++ b/lib/portage/tests/lazyimport/test_lazy_import_portage_baseline.py
@@ -18,6 +18,7 @@ class LazyImportPortageBaselineTestCase(TestCase):
     _baseline_imports = frozenset(
         [
             "portage.const",
+            "portage.installation",
             "portage.localization",
             "portage.proxy",
             "portage.proxy.lazyimport",

diff --git a/lib/portage/tests/lint/meson.build 
b/lib/portage/tests/lint/meson.build
new file mode 100644
index 000000000..5d7a72675
--- /dev/null
+++ b/lib/portage/tests/lint/meson.build
@@ -0,0 +1,12 @@
+py.install_sources(
+    [
+        'metadata.py',
+        'test_bash_syntax.py',
+        'test_compile_modules.py',
+        'test_import_modules.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/lint',
+    pure : false
+)

diff --git a/lib/portage/tests/locks/meson.build 
b/lib/portage/tests/locks/meson.build
new file mode 100644
index 000000000..17ec727a6
--- /dev/null
+++ b/lib/portage/tests/locks/meson.build
@@ -0,0 +1,10 @@
+py.install_sources(
+    [
+        'test_asynchronous_lock.py',
+        'test_lock_nonblock.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/locks',
+    pure : false
+)

diff --git a/lib/portage/tests/meson.build b/lib/portage/tests/meson.build
new file mode 100644
index 000000000..86e8e71ce
--- /dev/null
+++ b/lib/portage/tests/meson.build
@@ -0,0 +1,32 @@
+py.install_sources(
+    [
+        'conftest.py',
+        'runTests.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/tests',
+    pure : false
+)
+
+subdir('bin')
+subdir('dbapi')
+subdir('dep')
+subdir('ebuild')
+subdir('emerge')
+subdir('env')
+subdir('glsa')
+subdir('gpkg')
+subdir('lafilefixer')
+subdir('lazyimport')
+subdir('lint')
+subdir('locks')
+subdir('news')
+subdir('process')
+subdir('resolver')
+subdir('sets')
+subdir('sync')
+subdir('unicode')
+subdir('update')
+subdir('util')
+subdir('versions')
+subdir('xpak')

diff --git a/lib/portage/tests/news/meson.build 
b/lib/portage/tests/news/meson.build
new file mode 100644
index 000000000..25e4f9ef4
--- /dev/null
+++ b/lib/portage/tests/news/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'test_NewsItem.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/news',
+    pure : false
+)

diff --git a/lib/portage/tests/process/meson.build 
b/lib/portage/tests/process/meson.build
new file mode 100644
index 000000000..08e51d770
--- /dev/null
+++ b/lib/portage/tests/process/meson.build
@@ -0,0 +1,16 @@
+py.install_sources(
+    [
+        'test_AsyncFunction.py',
+        'test_PipeLogger.py',
+        'test_PopenProcessBlockingIO.py',
+        'test_PopenProcess.py',
+        'test_poll.py',
+        'test_spawn_fail_e2big.py',
+        'test_spawn_warn_large_env.py',
+        'test_unshare_net.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/process',
+    pure : false
+)

diff --git a/lib/portage/tests/resolver/binpkg_multi_instance/meson.build 
b/lib/portage/tests/resolver/binpkg_multi_instance/meson.build
new file mode 100644
index 000000000..7c4306e4a
--- /dev/null
+++ b/lib/portage/tests/resolver/binpkg_multi_instance/meson.build
@@ -0,0 +1,10 @@
+py.install_sources(
+    [
+        'test_build_id_profile_format.py',
+        'test_rebuilt_binaries.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/resolver/binpkg_multi_instance',
+    pure : false
+)

diff --git a/lib/portage/tests/resolver/meson.build 
b/lib/portage/tests/resolver/meson.build
new file mode 100644
index 000000000..5653e5066
--- /dev/null
+++ b/lib/portage/tests/resolver/meson.build
@@ -0,0 +1,96 @@
+py.install_sources(
+    [
+        'ResolverPlayground.py',
+        'test_aggressive_backtrack_downgrade.py',
+        'test_autounmask.py',
+        'test_autounmask_binpkg_use.py',
+        'test_autounmask_keep_keywords.py',
+        'test_autounmask_multilib_use.py',
+        'test_autounmask_parent.py',
+        'test_autounmask_use_backtrack.py',
+        'test_autounmask_use_breakage.py',
+        'test_autounmask_use_slot_conflict.py',
+        'test_backtracking.py',
+        'test_bdeps.py',
+        'test_binary_pkg_ebuild_visibility.py',
+        'test_blocker.py',
+        'test_changed_deps.py',
+        'test_circular_choices.py',
+        'test_circular_choices_rust.py',
+        'test_circular_dependencies.py',
+        'test_complete_graph.py',
+        'test_complete_if_new_subslot_without_revbump.py',
+        'test_depclean.py',
+        'test_depclean_order.py',
+        'test_depclean_slot_unavailable.py',
+        'test_depth.py',
+        'test_disjunctive_depend_order.py',
+        'test_eapi.py',
+        'test_features_test_use.py',
+        'test_imagemagick_graphicsmagick.py',
+        'test_installkernel.py',
+        'test_keywords.py',
+        'test_merge_order.py',
+        'test_missing_iuse_and_evaluated_atoms.py',
+        'test_multirepo.py',
+        'test_multislot.py',
+        'test_old_dep_chain_display.py',
+        'test_onlydeps.py',
+        'test_onlydeps_circular.py',
+        'test_onlydeps_ideps.py',
+        'test_onlydeps_minimal.py',
+        'test_or_choices.py',
+        'test_or_downgrade_installed.py',
+        'test_or_upgrade_installed.py',
+        'test_output.py',
+        'test_package_tracker.py',
+        'test_perl_rebuild_bug.py',
+        'test_profile_default_eapi.py',
+        'test_profile_package_set.py',
+        'test_rebuild.py',
+        'test_regular_slot_change_without_revbump.py',
+        'test_required_use.py',
+        'test_runtime_cycle_merge_order.py',
+        'test_simple.py',
+        'test_slot_abi.py',
+        'test_slot_abi_downgrade.py',
+        'test_slot_change_without_revbump.py',
+        'test_slot_collisions.py',
+        'test_slot_conflict_blocked_prune.py',
+        'test_slot_conflict_force_rebuild.py',
+        'test_slot_conflict_mask_update.py',
+        'test_slot_conflict_rebuild.py',
+        'test_slot_conflict_unsatisfied_deep_deps.py',
+        'test_slot_conflict_update.py',
+        'test_slot_conflict_update_virt.py',
+        'test_slot_operator_autounmask.py',
+        'test_slot_operator_bdeps.py',
+        'test_slot_operator_complete_graph.py',
+        'test_slot_operator_exclusive_slots.py',
+        'test_slot_operator_missed_update.py',
+        'test_slot_operator_rebuild.py',
+        'test_slot_operator_required_use.py',
+        'test_slot_operator_reverse_deps.py',
+        'test_slot_operator_runtime_pkg_mask.py',
+        'test_slot_operator_unsatisfied.py',
+        'test_slot_operator_unsolved.py',
+        'test_slot_operator_update_probe_parent_downgrade.py',
+        'test_solve_non_slot_operator_slot_conflicts.py',
+        'test_targetroot.py',
+        'test_unmerge_order.py',
+        'test_unnecessary_slot_upgrade.py',
+        'test_update.py',
+        'test_useflags.py',
+        'test_use_dep_defaults.py',
+        'test_virtual_minimize_children.py',
+        'test_virtual_slot.py',
+        'test_with_test_deps.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/resolver',
+    pure : false
+)
+
+subdir('binpkg_multi_instance')
+subdir('soname')

diff --git a/lib/portage/tests/resolver/soname/meson.build 
b/lib/portage/tests/resolver/soname/meson.build
new file mode 100644
index 000000000..3c3245bcc
--- /dev/null
+++ b/lib/portage/tests/resolver/soname/meson.build
@@ -0,0 +1,19 @@
+py.install_sources(
+    [
+        'test_autounmask.py',
+        'test_depclean.py',
+        'test_downgrade.py',
+        'test_or_choices.py',
+        'test_reinstall.py',
+        'test_skip_update.py',
+        'test_slot_conflict_reinstall.py',
+        'test_slot_conflict_update.py',
+        'test_soname_provided.py',
+        'test_unsatisfiable.py',
+        'test_unsatisfied.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/resolver/soname',
+    pure : false
+)

diff --git a/lib/portage/tests/runTests.py b/lib/portage/tests/runTests.py
index bf4c2a7c5..36ea3a791 100644
--- a/lib/portage/tests/runTests.py
+++ b/lib/portage/tests/runTests.py
@@ -11,7 +11,6 @@ import signal
 import tempfile
 import shutil
 import sys
-from distutils.dir_util import copy_tree
 
 
 def debug_signal(signum, frame):
@@ -63,7 +62,11 @@ if insert_bin_path:
 # Copy GPG test keys to temporary directory
 gpg_path = tempfile.mkdtemp(prefix="gpg_")
 
-copy_tree(os.path.join(os.path.dirname(os.path.realpath(__file__)), ".gnupg"), 
gpg_path)
+shutil.copytree(
+    os.path.join(os.path.dirname(os.path.realpath(__file__)), ".gnupg"),
+    gpg_path,
+    dirs_exist_ok=True,
+)
 
 os.chmod(gpg_path, 0o700)
 os.environ["PORTAGE_GNUPGHOME"] = gpg_path

diff --git a/lib/portage/tests/sets/base/meson.build 
b/lib/portage/tests/sets/base/meson.build
new file mode 100644
index 000000000..ba15a8213
--- /dev/null
+++ b/lib/portage/tests/sets/base/meson.build
@@ -0,0 +1,10 @@
+py.install_sources(
+    [
+        'testInternalPackageSet.py',
+        'testVariableSet.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/sets/base',
+    pure : false
+)

diff --git a/lib/portage/tests/sets/files/meson.build 
b/lib/portage/tests/sets/files/meson.build
new file mode 100644
index 000000000..0ac7449db
--- /dev/null
+++ b/lib/portage/tests/sets/files/meson.build
@@ -0,0 +1,10 @@
+py.install_sources(
+    [
+        'testConfigFileSet.py',
+        'testStaticFileSet.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/sets/files',
+    pure : false
+)

diff --git a/lib/portage/tests/sets/meson.build 
b/lib/portage/tests/sets/meson.build
new file mode 100644
index 000000000..a7df0bd82
--- /dev/null
+++ b/lib/portage/tests/sets/meson.build
@@ -0,0 +1,12 @@
+py.install_sources(
+    [
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/sets',
+    pure : false
+)
+
+subdir('base')
+subdir('files')
+subdir('shell')

diff --git a/lib/portage/tests/sets/shell/meson.build 
b/lib/portage/tests/sets/shell/meson.build
new file mode 100644
index 000000000..6044f3ebe
--- /dev/null
+++ b/lib/portage/tests/sets/shell/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'testShell.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/sets/shell',
+    pure : false
+)

diff --git a/lib/portage/tests/sync/meson.build 
b/lib/portage/tests/sync/meson.build
new file mode 100644
index 000000000..9de7cc551
--- /dev/null
+++ b/lib/portage/tests/sync/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'test_sync_local.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/sync',
+    pure : false
+)

diff --git a/lib/portage/tests/unicode/meson.build 
b/lib/portage/tests/unicode/meson.build
new file mode 100644
index 000000000..94303a376
--- /dev/null
+++ b/lib/portage/tests/unicode/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'test_string_format.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/unicode',
+    pure : false
+)

diff --git a/lib/portage/tests/update/meson.build 
b/lib/portage/tests/update/meson.build
new file mode 100644
index 000000000..0c5fb01a9
--- /dev/null
+++ b/lib/portage/tests/update/meson.build
@@ -0,0 +1,11 @@
+py.install_sources(
+    [
+        'test_move_ent.py',
+        'test_move_slot_ent.py',
+        'test_update_dbentry.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/update',
+    pure : false
+)

diff --git a/lib/portage/tests/util/dyn_libs/meson.build 
b/lib/portage/tests/util/dyn_libs/meson.build
new file mode 100644
index 000000000..51fc7c381
--- /dev/null
+++ b/lib/portage/tests/util/dyn_libs/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'test_soname_deps.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/util/dyn_libs',
+    pure : false
+)

diff --git a/lib/portage/tests/util/eventloop/meson.build 
b/lib/portage/tests/util/eventloop/meson.build
new file mode 100644
index 000000000..77ce25afe
--- /dev/null
+++ b/lib/portage/tests/util/eventloop/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'test_call_soon_fifo.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/util/eventloop',
+    pure : false
+)

diff --git a/lib/portage/tests/util/file_copy/meson.build 
b/lib/portage/tests/util/file_copy/meson.build
new file mode 100644
index 000000000..4c46e549d
--- /dev/null
+++ b/lib/portage/tests/util/file_copy/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'test_copyfile.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/util/file_copy',
+    pure : false
+)

diff --git a/lib/portage/tests/util/futures/asyncio/meson.build 
b/lib/portage/tests/util/futures/asyncio/meson.build
new file mode 100644
index 000000000..347088246
--- /dev/null
+++ b/lib/portage/tests/util/futures/asyncio/meson.build
@@ -0,0 +1,15 @@
+py.install_sources(
+    [
+        'test_child_watcher.py',
+        'test_event_loop_in_fork.py',
+        'test_pipe_closed.py',
+        'test_policy_wrapper_recursion.py',
+        'test_run_until_complete.py',
+        'test_subprocess_exec.py',
+        'test_wakeup_fd_sigchld.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/util/futures/asyncio',
+    pure : false
+)

diff --git a/lib/portage/tests/util/futures/meson.build 
b/lib/portage/tests/util/futures/meson.build
new file mode 100644
index 000000000..d927acbdd
--- /dev/null
+++ b/lib/portage/tests/util/futures/meson.build
@@ -0,0 +1,15 @@
+py.install_sources(
+    [
+        'test_compat_coroutine.py',
+        'test_done_callback.py',
+        'test_done_callback_after_exit.py',
+        'test_iter_completed.py',
+        'test_retry.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/util/futures',
+    pure : false
+)
+
+subdir('asyncio')

diff --git a/lib/portage/tests/util/meson.build 
b/lib/portage/tests/util/meson.build
new file mode 100644
index 000000000..65ed3ed1e
--- /dev/null
+++ b/lib/portage/tests/util/meson.build
@@ -0,0 +1,31 @@
+py.install_sources(
+    [
+        'test_checksum.py',
+        'test_digraph.py',
+        'test_file_copier.py',
+        'test_getconfig.py',
+        'test_grabdict.py',
+        'test_install_mask.py',
+        'test_manifest.py',
+        'test_mtimedb.py',
+        'test_normalizedPath.py',
+        'test_shelve.py',
+        'test_socks5.py',
+        'test_stackDictList.py',
+        'test_stackDicts.py',
+        'test_stackLists.py',
+        'test_uniqueArray.py',
+        'test_varExpand.py',
+        'test_whirlpool.py',
+        'test_xattr.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/util',
+    pure : false
+)
+
+subdir('dyn_libs')
+subdir('eventloop')
+subdir('file_copy')
+subdir('futures')

diff --git a/lib/portage/tests/versions/meson.build 
b/lib/portage/tests/versions/meson.build
new file mode 100644
index 000000000..4d1ed6e8d
--- /dev/null
+++ b/lib/portage/tests/versions/meson.build
@@ -0,0 +1,10 @@
+py.install_sources(
+    [
+        'test_cpv_sort_key.py',
+        'test_vercmp.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/versions',
+    pure : false
+)

diff --git a/lib/portage/tests/xpak/meson.build 
b/lib/portage/tests/xpak/meson.build
new file mode 100644
index 000000000..6563693d8
--- /dev/null
+++ b/lib/portage/tests/xpak/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'test_decodeint.py',
+        '__init__.py',
+        '__test__.py',
+    ],
+    subdir : 'portage/tests/xpak',
+    pure : false
+)

diff --git a/lib/portage/util/_async/meson.build 
b/lib/portage/util/_async/meson.build
new file mode 100644
index 000000000..1847cab26
--- /dev/null
+++ b/lib/portage/util/_async/meson.build
@@ -0,0 +1,20 @@
+py.install_sources(
+    [
+        'AsyncFunction.py',
+        'AsyncScheduler.py',
+        'AsyncTaskFuture.py',
+        'BuildLogger.py',
+        'FileCopier.py',
+        'FileDigester.py',
+        'ForkProcess.py',
+        'PipeLogger.py',
+        'PipeReaderBlockingIO.py',
+        'PopenProcess.py',
+        'SchedulerInterface.py',
+        'TaskScheduler.py',
+        'run_main_scheduler.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/util/_async',
+    pure : false
+)

diff --git a/lib/portage/util/_dyn_libs/meson.build 
b/lib/portage/util/_dyn_libs/meson.build
new file mode 100644
index 000000000..f9261b41c
--- /dev/null
+++ b/lib/portage/util/_dyn_libs/meson.build
@@ -0,0 +1,14 @@
+py.install_sources(
+    [
+        'LinkageMapELF.py',
+        'NeededEntry.py',
+        'PreservedLibsRegistry.py',
+        'display_preserved_libs.py',
+        'dyn_libs.py',
+        'soname_deps.py',
+        'soname_deps_qa.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/util/_dyn_libs',
+    pure : false
+)

diff --git a/lib/portage/util/_eventloop/meson.build 
b/lib/portage/util/_eventloop/meson.build
new file mode 100644
index 000000000..8a7f1d71b
--- /dev/null
+++ b/lib/portage/util/_eventloop/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'asyncio_event_loop.py',
+        'global_event_loop.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/util/_eventloop',
+    pure : false
+)

diff --git a/lib/portage/util/elf/meson.build b/lib/portage/util/elf/meson.build
new file mode 100644
index 000000000..cc6aa1e38
--- /dev/null
+++ b/lib/portage/util/elf/meson.build
@@ -0,0 +1,9 @@
+py.install_sources(
+    [
+        'constants.py',
+        'header.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/util/elf',
+    pure : false
+)

diff --git a/lib/portage/util/endian/meson.build 
b/lib/portage/util/endian/meson.build
new file mode 100644
index 000000000..8bdda8052
--- /dev/null
+++ b/lib/portage/util/endian/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'decode.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/util/endian',
+    pure : false
+)

diff --git a/lib/portage/util/file_copy/meson.build 
b/lib/portage/util/file_copy/meson.build
new file mode 100644
index 000000000..fcaeee21f
--- /dev/null
+++ b/lib/portage/util/file_copy/meson.build
@@ -0,0 +1,7 @@
+py.install_sources(
+    [
+        '__init__.py',
+    ],
+    subdir : 'portage/util/file_copy',
+    pure : false
+)

diff --git a/lib/portage/util/futures/_asyncio/meson.build 
b/lib/portage/util/futures/_asyncio/meson.build
new file mode 100644
index 000000000..5eb23c61f
--- /dev/null
+++ b/lib/portage/util/futures/_asyncio/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'streams.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/util/futures/_asyncio',
+    pure : false
+)

diff --git a/lib/portage/util/futures/executor/meson.build 
b/lib/portage/util/futures/executor/meson.build
new file mode 100644
index 000000000..ab166e935
--- /dev/null
+++ b/lib/portage/util/futures/executor/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'fork.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/util/futures/executor',
+    pure : false
+)

diff --git a/lib/portage/util/futures/meson.build 
b/lib/portage/util/futures/meson.build
new file mode 100644
index 000000000..3296c25b2
--- /dev/null
+++ b/lib/portage/util/futures/meson.build
@@ -0,0 +1,17 @@
+py.install_sources(
+    [
+        'compat_coroutine.py',
+        'extendedfutures.py',
+        'futures.py',
+        'iter_completed.py',
+        'retry.py',
+        'unix_events.py',
+        '_sync_decorator.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/util/futures',
+    pure : false
+)
+
+subdir('executor')
+subdir('_asyncio')

diff --git a/lib/portage/util/iterators/meson.build 
b/lib/portage/util/iterators/meson.build
new file mode 100644
index 000000000..013fe7a58
--- /dev/null
+++ b/lib/portage/util/iterators/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'MultiIterGroupBy.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/util/iterators',
+    pure : false
+)

diff --git a/lib/portage/util/meson.build b/lib/portage/util/meson.build
new file mode 100644
index 000000000..c0a4942f1
--- /dev/null
+++ b/lib/portage/util/meson.build
@@ -0,0 +1,49 @@
+py.install_sources(
+    [
+        'ExtractKernelVersion.py',
+        'SlotObject.py',
+        'backoff.py',
+        'bin_entry_point.py',
+        'changelog.py',
+        'compression_probe.py',
+        'configparser.py',
+        'cpuinfo.py',
+        'digraph.py',
+        'env_update.py',
+        'formatter.py',
+        'hooks.py',
+        'install_mask.py',
+        'lafilefixer.py',
+        'listdir.py',
+        'locale.py',
+        'movefile.py',
+        'mtimedb.py',
+        'netlink.py',
+        'path.py',
+        'shelve.py',
+        'socks5.py',
+        'whirlpool.py',
+        'writeable_check.py',
+        '_compare_files.py',
+        '_ctypes.py',
+        '_desktop_entry.py',
+        '_get_vm_info.py',
+        '_info_files.py',
+        '_path.py',
+        '_pty.py',
+        '_urlopen.py',
+        '_xattr.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/util',
+    pure : false
+)
+
+subdir('elf')
+subdir('endian')
+subdir('file_copy')
+subdir('futures')
+subdir('iterators')
+subdir('_async')
+subdir('_dyn_libs')
+subdir('_eventloop')

diff --git a/lib/portage/xml/meson.build b/lib/portage/xml/meson.build
new file mode 100644
index 000000000..40c92385f
--- /dev/null
+++ b/lib/portage/xml/meson.build
@@ -0,0 +1,8 @@
+py.install_sources(
+    [
+        'metadata.py',
+        '__init__.py',
+    ],
+    subdir : 'portage/xml',
+    pure : false
+)

diff --git a/man/color.map.5 b/man/color.map.5
index 92a1baa91..eccb0c5a9 100644
--- a/man/color.map.5
+++ b/man/color.map.5
@@ -1,4 +1,4 @@
-.TH "COLOR.MAP" "5" "Jul 2013" "Portage VERSION" "Portage"
+.TH "COLOR.MAP" "5" "Jul 2013" "Portage @VERSION@" "Portage"
 .SH "NAME"
 color.map \- custom color settings for Portage
 .SH "SYNOPSIS"

diff --git a/man/dispatch-conf.1 b/man/dispatch-conf.1
index b877b6942..146b86540 100644
--- a/man/dispatch-conf.1
+++ b/man/dispatch-conf.1
@@ -1,4 +1,4 @@
-.TH "DISPATCH-CONF" "1" "Jan 2011" "Portage VERSION" "Portage"
+.TH "DISPATCH-CONF" "1" "Jan 2011" "Portage @VERSION@" "Portage"
 .SH "NAME"
 dispatch\-conf \- Sanely update configuration files after emerging new packages
 .SH "SYNOPSIS"

diff --git a/man/ebuild.1 b/man/ebuild.1
index 09974b755..165a48219 100644
--- a/man/ebuild.1
+++ b/man/ebuild.1
@@ -1,4 +1,4 @@
-.TH "EBUILD" "1" "Mar 2023" "Portage VERSION" "Portage"
+.TH "EBUILD" "1" "Mar 2023" "Portage @VERSION@" "Portage"
 .SH "NAME"
 ebuild \- a low level interface to the Portage system
 .SH "SYNOPSIS"

diff --git a/man/ebuild.5 b/man/ebuild.5
index fe70e40f5..d4187a8a4 100644
--- a/man/ebuild.5
+++ b/man/ebuild.5
@@ -1,4 +1,4 @@
-.TH "EBUILD" "5" "Apr 2023" "Portage VERSION" "Portage"
+.TH "EBUILD" "5" "Apr 2023" "Portage @VERSION@" "Portage"
 
 .SH "NAME"
 ebuild \- the internal format, variables, and functions in an ebuild script

diff --git a/man/egencache.1 b/man/egencache.1
index 79c1c3159..977ccca07 100644
--- a/man/egencache.1
+++ b/man/egencache.1
@@ -1,4 +1,4 @@
-.TH "EGENCACHE" "1" "Sep 2020" "Portage VERSION" "Portage"
+.TH "EGENCACHE" "1" "Sep 2020" "Portage @VERSION@" "Portage"
 .SH "NAME"
 egencache \- generate metadata cache for ebuild repositories
 .SH "SYNOPSIS"

diff --git a/man/emaint.1 b/man/emaint.1
index 682487c74..2abba9d47 100644
--- a/man/emaint.1
+++ b/man/emaint.1
@@ -1,4 +1,4 @@
-.TH "EMAINT" "1" "Feb 2021" "Portage VERSION" "Portage"
+.TH "EMAINT" "1" "Feb 2021" "Portage @VERSION@" "Portage"
 .SH NAME
 emaint \- performs package management related system health checks and 
maintenance
 .SH SYNOPSIS

diff --git a/man/emerge.1 b/man/emerge.1
index 191ea4757..c9f4e4542 100644
--- a/man/emerge.1
+++ b/man/emerge.1
@@ -1,4 +1,4 @@
-.TH "EMERGE" "1" "Mar 2023" "Portage VERSION" "Portage"
+.TH "EMERGE" "1" "Mar 2023" "Portage @VERSION@" "Portage"
 .SH "NAME"
 emerge \- Command\-line interface to the Portage system
 .SH "SYNOPSIS"

diff --git a/man/emirrordist.1 b/man/emirrordist.1
index d66a1849d..37a56a8bb 100644
--- a/man/emirrordist.1
+++ b/man/emirrordist.1
@@ -1,4 +1,4 @@
-.TH "EMIRRORDIST" "1" "Feb 2021" "Portage VERSION" "Portage"
+.TH "EMIRRORDIST" "1" "Feb 2021" "Portage @VERSION@" "Portage"
 .SH "NAME"
 emirrordist \- a fetch tool for mirroring of package distfiles
 .SH SYNOPSIS

diff --git a/man/env-update.1 b/man/env-update.1
index f60945d18..ba9a4c9ab 100644
--- a/man/env-update.1
+++ b/man/env-update.1
@@ -1,4 +1,4 @@
-.TH "ENV-UPDATE" "1" "Aug 2008" "Portage VERSION" "Portage"
+.TH "ENV-UPDATE" "1" "Aug 2008" "Portage @VERSION@" "Portage"
 .SH "NAME"
 env\-update \- updates environment settings automatically
 .SH "SYNOPSIS"

diff --git a/man/etc-update.1 b/man/etc-update.1
index fd6568a03..9b82e1f5c 100644
--- a/man/etc-update.1
+++ b/man/etc-update.1
@@ -1,4 +1,4 @@
-.TH "ETC-UPDATE" "1" "Mar 2012" "Portage VERSION" "Portage"
+.TH "ETC-UPDATE" "1" "Mar 2012" "Portage @VERSION@" "Portage"
 .SH "NAME"
 etc\-update \- handle configuration file updates
 .SH "SYNOPSIS"

diff --git a/man/fixpackages.1 b/man/fixpackages.1
index 4797810cb..8ae6f900b 100644
--- a/man/fixpackages.1
+++ b/man/fixpackages.1
@@ -1,4 +1,4 @@
-.TH "FIXPACKAGES" "1" "Dec 2011" "Portage VERSION" "Portage"
+.TH "FIXPACKAGES" "1" "Dec 2011" "Portage @VERSION@" "Portage"
 .SH NAME
 fixpackages \- Perform package move updates for all packages
 .SH SYNOPSIS

diff --git a/man/glsa-check.1 b/man/glsa-check.1
index 4fe1ad506..acd9bbada 100644
--- a/man/glsa-check.1
+++ b/man/glsa-check.1
@@ -1,4 +1,4 @@
-.TH "GLSA-CHECK" "1" "September 2019" "Portage VERSION" "Portage"
+.TH "GLSA-CHECK" "1" "September 2019" "Portage @VERSION@" "Portage"
 .SH "NAME"
 \fBglsa\-check\fR \- Tool to locally monitor and manage GLSAs
 .SH "SYNOPSIS"

diff --git a/man/make.conf.5 b/man/make.conf.5
index dbab292fb..75206d5e7 100644
--- a/man/make.conf.5
+++ b/man/make.conf.5
@@ -1,4 +1,4 @@
-.TH "MAKE.CONF" "5" "Mar 2023" "Portage VERSION" "Portage"
+.TH "MAKE.CONF" "5" "Mar 2023" "Portage @VERSION@" "Portage"
 .SH "NAME"
 make.conf \- custom settings for Portage
 .SH "SYNOPSIS"

diff --git a/man/meson.build b/man/meson.build
new file mode 100644
index 000000000..0ae8df70a
--- /dev/null
+++ b/man/meson.build
@@ -0,0 +1,31 @@
+man_pages_out = []
+man_pages_in = [
+    'color.map.5',
+    'dispatch-conf.1',
+    'ebuild.1',
+    'ebuild.5',
+    'egencache.1',
+    'emaint.1',
+    'emerge.1',
+    'emirrordist.1',
+    'env-update.1',
+    'etc-update.1',
+    'fixpackages.1',
+    'glsa-check.1',
+    'make.conf.5',
+    'portage.5',
+    'quickpkg.1',
+    'xpak.5',
+]
+
+foreach man_page : man_pages_in
+    man_pages_out += configure_file(
+        input : man_page,
+        output : man_page,
+        configuration : conf_data
+    )
+endforeach
+
+install_man(man_pages_out)
+
+subdir('ru')

diff --git a/man/portage.5 b/man/portage.5
index a1358e376..4bae67720 100644
--- a/man/portage.5
+++ b/man/portage.5
@@ -1,4 +1,4 @@
-.TH "PORTAGE" "5" "Apr 2023" "Portage VERSION" "Portage"
+.TH "PORTAGE" "5" "Apr 2023" "Portage @VERSION@" "Portage"
 .SH NAME
 portage \- the heart of Gentoo
 .SH "DESCRIPTION"

diff --git a/man/quickpkg.1 b/man/quickpkg.1
index a6aff7c50..5f3797a04 100644
--- a/man/quickpkg.1
+++ b/man/quickpkg.1
@@ -1,4 +1,4 @@
-.TH "QUICKPKG" "1" "Apr 2019" "Portage VERSION" "Portage"
+.TH "QUICKPKG" "1" "Apr 2019" "Portage @VERSION@" "Portage"
 .SH NAME
 quickpkg \- creates portage packages
 .SH SYNOPSIS

diff --git a/man/ru/color.map.5 b/man/ru/color.map.5
index 7849ffdba..14a915ddd 100644
--- a/man/ru/color.map.5
+++ b/man/ru/color.map.5
@@ -1,4 +1,4 @@
-.TH "COLOR.MAP" "5" "Jul 2013" "Portage VERSION" "Portage"
+.TH "COLOR.MAP" "5" "Jul 2013" "Portage @VERSION@" "Portage"
 .SH "НАЗВАНИЕ"
 color.map \- пользовательские настройки цвета в Portage
 .SH "ПАРАМЕТРЫ"

diff --git a/man/ru/dispatch-conf.1 b/man/ru/dispatch-conf.1
index 99ab06932..b587f87d2 100644
--- a/man/ru/dispatch-conf.1
+++ b/man/ru/dispatch-conf.1
@@ -1,4 +1,4 @@
-.TH "DISPATCH-CONF" "1" "Jan 2011" "Portage VERSION" "Portage"
+.TH "DISPATCH-CONF" "1" "Jan 2011" "Portage @VERSION@" "Portage"
 .SH "НАЗВАНИЕ"
 dispatch\-conf \- безопасное обновление конфигурационных файлов после
 установки новых пакетов

diff --git a/man/ru/ebuild.1 b/man/ru/ebuild.1
index b82a90814..39de5b0b9 100644
--- a/man/ru/ebuild.1
+++ b/man/ru/ebuild.1
@@ -1,4 +1,4 @@
-.TH "EBUILD" "1" "Mar 2023" "Portage VERSION" "Portage"
+.TH "EBUILD" "1" "Mar 2023" "Portage @VERSION@" "Portage"
 .SH "НАЗВАНИЕ"
 ebuild \- низкоуровневый интерфейс системы Portage
 .SH "СИНТАКСИС"

diff --git a/man/ru/env-update.1 b/man/ru/env-update.1
index e1e584c9a..316590f0c 100644
--- a/man/ru/env-update.1
+++ b/man/ru/env-update.1
@@ -1,4 +1,4 @@
-.TH "ENV-UPDATE" "1" "Aug 2008" "Portage VERSION" "Portage"
+.TH "ENV-UPDATE" "1" "Aug 2008" "Portage @VERSION@" "Portage"
 .SH "НАЗВАНИЕ"
 env\-update \- автоматическое обновление настроек окружения
 .SH "СИНТАКСИС"

diff --git a/man/ru/etc-update.1 b/man/ru/etc-update.1
index dcc641fbb..ba276a431 100644
--- a/man/ru/etc-update.1
+++ b/man/ru/etc-update.1
@@ -1,4 +1,4 @@
-.TH "ETC-UPDATE" "1" "Mar 2012" "Portage VERSION" "Portage"
+.TH "ETC-UPDATE" "1" "Mar 2012" "Portage @VERSION@" "Portage"
 .SH "НАЗВАНИЕ"
 etc\-update \- обработка изменений конфигурационных файлов
 .SH "СИНТАКСИС"

diff --git a/man/ru/fixpackages.1 b/man/ru/fixpackages.1
index 606e285c5..e4d1fcecd 100644
--- a/man/ru/fixpackages.1
+++ b/man/ru/fixpackages.1
@@ -1,4 +1,4 @@
-.TH "FIXPACKAGES" "1" "Dec 2011" "Portage VERSION" "Portage"
+.TH "FIXPACKAGES" "1" "Dec 2011" "Portage @VERSION@" "Portage"
 .SH "НАЗВАНИЕ"
 fixpackages \- выполняет переносы пакетов при обновлениях
 для всех пакетов

diff --git a/man/ru/meson.build b/man/ru/meson.build
new file mode 100644
index 000000000..3a97db377
--- /dev/null
+++ b/man/ru/meson.build
@@ -0,0 +1,19 @@
+man_pages_out = []
+man_pages_in = [
+    'color.map.5',
+    'dispatch-conf.1',
+    'ebuild.1',
+    'env-update.1',
+    'etc-update.1',
+    'fixpackages.1',
+]
+
+foreach man_page : man_pages_in
+    man_pages_out += configure_file(
+        input : man_page,
+        output : man_page,
+        configuration : conf_data
+    )
+endforeach
+
+install_man(man_pages_out, locale : 'ru')

diff --git a/meson.build b/meson.build
new file mode 100644
index 000000000..db812eeaf
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,123 @@
+project(
+    'portage',
+    'c',
+    version : '3.0.49',
+    license : 'GPL-2.0-or-later',
+    meson_version : '>=0.58.0'
+)
+
+py_mod = import('python')
+# TODO: Add "pure : false" here instead of py.install_sources() when requiring 
Meson >=0.64.0.
+py = py_mod.find_installation()
+
+sed = find_program('sed', required : true)
+
+system_wide = get_option('system-wide')
+eprefix = get_option('eprefix')
+prefixdir = get_option('prefix')
+datadir = get_option('datadir')
+docdir = get_option('docdir')
+portage_base = get_option('portage-base')
+portage_bindir = get_option('portage-bindir')
+portage_datadir = get_option('portage-datadir')
+
+sysconfdir = system_wide ? get_option('sysconfdir') \
+                         : datadir / 'etc'
+
+if docdir == ''
+    docdir = system_wide ? datadir / 'doc' / 'portage' \
+                         : datadir / 'share' / 'portage' / 'doc'
+endif
+
+if portage_base == ''
+    # This path must be absolute when system-wide.
+    portage_base = system_wide ? prefixdir / 'lib' / 'portage' \
+                               : datadir / 'lib' / 'portage'
+endif
+
+if portage_bindir == ''
+    portage_bindir = portage_base / 'bin'
+endif
+
+if portage_datadir == ''
+    portage_datadir = system_wide ? datadir / 'portage' \
+                                  : datadir / 'share' / 'portage'
+endif
+
+# hprefixify is copied from prefix.eclass.
+dirs = '/(usr|lib(|[onx]?32|n?64)|etc|bin|sbin|var|opt|run)'
+hprefixify = [
+    sed, '-r',
+    '-e', 's,([^[:alnum:]}\\)\\.])' + dirs + ',\\1' + eprefix.replace(',', 
'\\,') + '/\\2,g',
+    '-e', 's,^' + dirs + ',' + eprefix.replace(',', '\\,') + '/\\1,',
+    '@INPUT@'
+]
+
+# Use Portage's own code to determine the version from git, if possible.
+version = run_command(
+    [py, '-c', 'import portage; print(portage.VERSION)'],
+    env : { 'PYTHONPATH' : meson.current_source_dir() / 'lib' },
+    capture : true,
+    check : false
+)
+
+# Fall back to the Meson project version above.
+if version.returncode() == 0
+    version = version.stdout().strip()
+    if version == 'HEAD'
+        version = ''
+    endif
+else
+    version = ''
+endif
+
+conf_data = configuration_data({
+    'VERSION' : version == '' ? meson.project_version() : version
+})
+
+if system_wide
+    conf_data.set('INSTALL_TYPE', 'SYSTEM')
+    conf_data.set('PORTAGE_BASE_PATH', portage_base)
+    conf_data.set('PORTAGE_BIN_PATH', portage_bindir)
+    conf_data.set('EPREFIX', eprefix)
+else
+    conf_data.set('INSTALL_TYPE', 'MODULE')
+    conf_data.set('PORTAGE_BASE_PATH', '')
+    conf_data.set('PORTAGE_BIN_PATH', '')
+    conf_data.set('EPREFIX', '')
+endif
+
+subdir('bin')
+subdir('lib')
+
+if get_option('native-extensions')
+    subdir('src')
+endif
+
+test(
+    'python',
+    py,
+    args : ['-bWd', meson.current_source_dir() / 'lib' / 'portage' / 'tests' / 
'runTests.py'],
+    timeout : 0
+)
+
+if get_option('code-only')
+    subdir_done()
+endif
+
+subdir('cnf')
+
+install_data(
+    [
+        'NEWS',
+        'RELEASE-NOTES'
+    ],
+    install_dir : docdir
+)
+
+if not system_wide
+    subdir_done()
+endif
+
+subdir('doc')
+subdir('man')

diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 000000000..a433a52e9
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,59 @@
+option('system-wide', type : 'boolean', value : true,
+    description : 'Install system-wide rather than isolated inside a Python 
environment'
+)
+
+option('code-only', type : 'boolean', value : false,
+    description : 'Do not install additional files such as configuration or 
documentation'
+)
+
+option('eprefix', type : 'string',
+    description : 'Prefix directory for Portage to operate under'
+)
+
+option('portage-base', type : 'string',
+    description : 'Portage installation base directory'
+)
+
+option('portage-bindir', type : 'string',
+    description : 'Internal Portage executables directory'
+)
+
+option('portage-datadir', type : 'string',
+    description : 'Data files directory'
+)
+
+option('docdir', type : 'string',
+    description : 'Documentation directory'
+)
+
+option('doc', type : 'boolean', value : false,
+    description : 'Build and install documentation'
+)
+
+option('doc-formats', type : 'array', choices : ['xhtml', 'xhtml-nochunks'],
+    description : 'Documentation formats to build'
+)
+
+option('apidoc', type : 'boolean', value : false,
+    description : 'Build and install API documentation'
+)
+
+option('native-extensions', type : 'boolean', value : true,
+    description : 'Build and install the native extensions for better 
performance'
+)
+
+option('gentoo-dev', type : 'boolean', value : false,
+    description : 'Enable features required for Gentoo ebuild development'
+)
+
+option('ipc', type : 'boolean', value : true,
+    description : 'Use inter-process communication between Portage and running 
ebuilds'
+)
+
+option('rsync-verify', type : 'boolean', value : true,
+    description : 'Enable full-tree cryptographic verification of Gentoo 
repository rsync checkouts'
+)
+
+option('xattr', type : 'boolean', value : false,
+    description : 'Preserve extended attributes when installing files'
+)

diff --git a/pyproject.toml b/pyproject.toml
index 646e59c96..635fca25c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,33 @@
-[build-system]
-requires = [
-       "setuptools",
-       "wheel",
+[project]
+name = 'portage'
+dynamic = ['version']
+description = 'Portage is the package management and distribution system for 
Gentoo'
+readme = 'README.md'
+requires-python = '>=3.9'
+license = {file = "LICENSE"}
+authors = [
+  {name = 'Gentoo Portage Development Team', email = 'dev-port...@gentoo.org'},
 ]
-build-backend = "setuptools.build_meta"
+
+[build-system]
+build-backend = 'mesonpy'
+requires = ['meson-python']
+
+[tool.meson-python.args]
+setup = ['-Dsystem-wide=false']
+
+[project.scripts]
+archive-conf = 'portage.util.bin_entry_point:bin_entry_point'
+dispatch-conf = 'portage.util.bin_entry_point:bin_entry_point'
+ebuild = 'portage.util.bin_entry_point:bin_entry_point'
+egencache = 'portage.util.bin_entry_point:bin_entry_point'
+emaint = 'portage.util.bin_entry_point:bin_entry_point'
+emerge = 'portage.util.bin_entry_point:bin_entry_point'
+emirrordist = 'portage.util.bin_entry_point:bin_entry_point'
+env-update = 'portage.util.bin_entry_point:bin_entry_point'
+fixpackages = 'portage.util.bin_entry_point:bin_entry_point'
+glsa-check = 'portage.util.bin_entry_point:bin_entry_point'
+gpkg-sign = 'portage.util.bin_entry_point:bin_entry_point'
+portageq = 'portage.util.bin_entry_point:bin_entry_point'
+quickpkg = 'portage.util.bin_entry_point:bin_entry_point'
+regenworld = 'portage.util.bin_entry_point:bin_entry_point'

diff --git a/setup.py b/setup.py
deleted file mode 100755
index 4525264c7..000000000
--- a/setup.py
+++ /dev/null
@@ -1,925 +0,0 @@
-#!/usr/bin/env python
-# Copyright 1998-2023 Gentoo Authors
-# Distributed under the terms of the GNU General Public License v2
-
-try:
-    from setuptools.core import setup, Command, Extension
-    from setuptools.command.build import build
-    from setuptools.command.build_ext import build_ext as _build_ext
-    from setuptools.command.build_scripts import build_scripts
-    from setuptools.command.clean import clean
-    from setuptools.command.install import install
-    from setuptools.command.install_data import install_data
-    from setuptools.command.install_lib import install_lib
-    from setuptools.command.install_scripts import install_scripts
-    from setuptools.command.sdist import sdist
-    from setuptools.dep_util import newer
-    from setuptools.dir_util import mkpath, remove_tree, copy_tree
-    from setuptools.util import change_root, subst_vars
-except ImportError:
-    from distutils.core import setup, Command, Extension
-    from distutils.command.build import build
-    from distutils.command.build_ext import build_ext as _build_ext
-    from distutils.command.build_scripts import build_scripts
-    from distutils.command.clean import clean
-    from distutils.command.install import install
-    from distutils.command.install_data import install_data
-    from distutils.command.install_lib import install_lib
-    from distutils.command.install_scripts import install_scripts
-    from distutils.command.sdist import sdist
-    from distutils.dep_util import newer
-    from distutils.dir_util import mkpath, remove_tree, copy_tree
-    from distutils.util import change_root, subst_vars
-
-import codecs
-import collections
-import glob
-import itertools
-import os
-import os.path
-import platform
-import re
-import subprocess
-import sys
-
-autodetect_pip = os.path.basename(os.environ.get("_", "")) == "pip" or 
os.path.basename(
-    os.path.dirname(__file__)
-).startswith("pip-")
-venv_prefix = "" if sys.prefix == sys.base_prefix else sys.prefix
-create_entry_points = bool(autodetect_pip or venv_prefix)
-with open(os.path.join(os.path.dirname(__file__), "README.md")) as f:
-    long_description = f.read()
-
-# TODO:
-# - smarter rebuilds of docs w/ 'install_docbook' and 'install_apidoc'.
-
-# Dictionary of scripts.  The structure is
-#   key   = location in filesystem to install the scripts
-#   value = list of scripts, path relative to top source directory
-x_scripts = {
-    "bin": [
-        "bin/ebuild",
-        "bin/egencache",
-        "bin/emerge",
-        "bin/emerge-webrsync",
-        "bin/emirrordist",
-        "bin/glsa-check",
-        "bin/portageq",
-        "bin/quickpkg",
-        "bin/gpkg-sign",
-    ],
-    "sbin": [
-        "bin/archive-conf",
-        "bin/dispatch-conf",
-        "bin/emaint",
-        "bin/env-update",
-        "bin/etc-update",
-        "bin/fixpackages",
-        "bin/regenworld",
-    ],
-}
-
-# Dictionary custom modules written in C/C++ here.  The structure is
-#   key   = module name
-#   value = list of C/C++ source code, path relative to top source directory
-x_c_helpers = {
-    "portage.util.libc": [
-        "src/portage_util_libc.c",
-    ],
-    "portage.util._whirlpool": [
-        "src/portage_util__whirlpool.c",
-    ],
-}
-
-if platform.system() == "Linux":
-    x_c_helpers.update(
-        {
-            "portage.util.file_copy.reflink_linux": [
-                "src/portage_util_file_copy_reflink_linux.c",
-            ],
-        }
-    )
-
-
-class x_build(build):
-    """Build command with extra build_man call."""
-
-    def run(self):
-        build.run(self)
-        self.run_command("build_man")
-
-
-class build_man(Command):
-    """Perform substitutions in manpages."""
-
-    user_options = []
-
-    def initialize_options(self):
-        self.build_base = None
-
-    def finalize_options(self):
-        self.set_undefined_options("build", ("build_base", "build_base"))
-
-    def run(self):
-        for d, files in self.distribution.data_files:
-            if not d.startswith("$mandir/"):
-                continue
-
-            for source in files:
-                target = os.path.join(self.build_base, source)
-                mkpath(os.path.dirname(target))
-
-                if not newer(source, target) and not newer(__file__, target):
-                    continue
-
-                print(f"copying and updating {source} -> {target}")
-
-                with codecs.open(source, "r", "utf8") as f:
-                    data = f.readlines()
-                data[0] = data[0].replace("VERSION", 
self.distribution.get_version())
-                with codecs.open(target, "w", "utf8") as f:
-                    f.writelines(data)
-
-
-class docbook(Command):
-    """Build docs using docbook."""
-
-    user_options = [
-        (
-            "doc-formats=",
-            None,
-            "Documentation formats to build (all xmlto formats for docbook are 
allowed, comma-separated",
-        ),
-    ]
-
-    def initialize_options(self):
-        self.doc_formats = "xhtml,xhtml-nochunks"
-
-    def finalize_options(self):
-        self.doc_formats = self.doc_formats.replace(",", " ").split()
-
-    def run(self):
-        if not os.path.isdir("doc/fragment"):
-            mkpath("doc/fragment")
-
-        with open("doc/fragment/date", "w"):
-            pass
-        with open("doc/fragment/version", "w") as f:
-            
f.write(f"<releaseinfo>{self.distribution.get_version()}</releaseinfo>")
-
-        for f in self.doc_formats:
-            print(f"Building docs in {f} format...")
-            subprocess.check_call(
-                ["xmlto", "-o", "doc", "-m", "doc/custom.xsl", f, 
"doc/portage.docbook"]
-            )
-
-
-class apidoc(Command):
-    """Build API docs using apidoc."""
-
-    user_options = []
-
-    def initialize_options(self):
-        self.build_lib = None
-
-    def finalize_options(self):
-        self.set_undefined_options("build_py", ("build_lib", "build_lib"))
-
-    def run(self):
-        self.run_command("build_py")
-
-        print("Building API documentation...")
-
-        process_env = os.environ.copy()
-        pythonpath = self.build_lib
-        try:
-            pythonpath += ":" + process_env["PYTHONPATH"]
-        except KeyError:
-            pass
-        process_env["PYTHONPATH"] = pythonpath
-
-        subprocess.check_call(["make", "-C", "doc/api", "html"], 
env=process_env)
-
-
-class install_docbook(install_data):
-    """install_data for docbook docs"""
-
-    user_options = install_data.user_options + [
-        ("htmldir=", None, "HTML documentation install directory"),
-    ]
-
-    def initialize_options(self):
-        install_data.initialize_options(self)
-        self.htmldir = None
-
-    def finalize_options(self):
-        self.set_undefined_options("install", ("htmldir", "htmldir"))
-        install_data.finalize_options(self)
-
-    def run(self):
-        if not os.path.exists("doc/portage.html"):
-            self.run_command("docbook")
-        self.data_files = [
-            (self.htmldir, glob.glob("doc/*.html")),
-        ]
-        install_data.run(self)
-
-
-class install_apidoc(install_data):
-    """install_data for apidoc docs"""
-
-    user_options = install_data.user_options + [
-        ("htmldir=", None, "HTML documentation install directory"),
-    ]
-
-    def initialize_options(self):
-        install_data.initialize_options(self)
-        self.htmldir = None
-
-    def finalize_options(self):
-        self.set_undefined_options("install", ("htmldir", "htmldir"))
-        install_data.finalize_options(self)
-
-    def run(self):
-        if not os.path.exists("doc/api/build/html/index.html"):
-            self.run_command("apidoc")
-        self.data_files = [
-            (
-                os.path.join(self.htmldir, "api"),
-                glob.glob("doc/api/build/html/*.html")
-                + glob.glob("doc/api/build/html/*.js"),
-            ),
-            (
-                os.path.join(self.htmldir, "api/_static"),
-                glob.glob("doc/api/build/html/_static/*"),
-            ),
-        ]
-        install_data.run(self)
-
-
-class x_build_scripts_custom(build_scripts):
-    def finalize_options(self):
-        build_scripts.finalize_options(self)
-        if "dir_name" in dir(self):
-            self.build_dir = os.path.join(self.build_dir, self.dir_name)
-            if self.dir_name in x_scripts:
-                self.scripts = x_scripts[self.dir_name]
-            else:
-                self.scripts = set(self.scripts)
-                if not (create_entry_points and self.dir_name == "portage"):
-                    for other_files in x_scripts.values():
-                        self.scripts.difference_update(other_files)
-
-    def run(self):
-        # group scripts by subdirectory
-        split_scripts = collections.defaultdict(list)
-        for f in self.scripts:
-            dir_name = os.path.dirname(f[len("bin/") :])
-            split_scripts[dir_name].append(f)
-
-        base_dir = self.build_dir
-        base_scripts = self.scripts
-        for d, files in split_scripts.items():
-            self.build_dir = os.path.join(base_dir, d)
-            self.scripts = files
-            self.copy_scripts()
-
-        # restore previous values
-        self.build_dir = base_dir
-        self.scripts = base_scripts
-
-
-class x_build_scripts_bin(x_build_scripts_custom):
-    dir_name = "bin"
-
-
-class x_build_scripts_sbin(x_build_scripts_custom):
-    dir_name = "sbin"
-
-
-class x_build_scripts_portagebin(x_build_scripts_custom):
-    dir_name = "portage"
-
-
-class x_build_scripts(build_scripts):
-    def initialize_option(self):
-        build_scripts.initialize_options(self)
-
-    def finalize_options(self):
-        build_scripts.finalize_options(self)
-
-    def run(self):
-        self.run_command("build_scripts_bin")
-        self.run_command("build_scripts_portagebin")
-        self.run_command("build_scripts_sbin")
-
-
-class x_clean(clean):
-    """clean extended for doc & post-test cleaning"""
-
-    @staticmethod
-    def clean_docs():
-        def get_doc_outfiles():
-            for dirpath, _dirnames, filenames in os.walk("doc"):
-                for f in filenames:
-                    if f.endswith(".docbook") or f == "custom.xsl":
-                        pass
-                    else:
-                        yield os.path.join(dirpath, f)
-
-                # do not recurse
-                break
-
-        for f in get_doc_outfiles():
-            print(f"removing {repr(f)}")
-            os.remove(f)
-
-        if os.path.isdir("doc/fragment"):
-            remove_tree("doc/fragment")
-
-        if os.path.isdir("doc/api/build"):
-            remove_tree("doc/api/build")
-
-    def clean_tests(self):
-        # do not remove incorrect dirs accidentally
-        top_dir = os.path.normpath(os.path.join(self.build_lib, ".."))
-        cprefix = os.path.commonprefix((self.build_base, top_dir))
-        if cprefix != self.build_base:
-            return
-
-        bin_dir = os.path.join(top_dir, "bin")
-        if os.path.exists(bin_dir):
-            remove_tree(bin_dir)
-
-        conf_dir = os.path.join(top_dir, "cnf")
-        if os.path.islink(conf_dir):
-            print(f"removing {repr(conf_dir)} symlink")
-            os.unlink(conf_dir)
-
-        pni_file = os.path.join(top_dir, ".portage_not_installed")
-        if os.path.exists(pni_file):
-            print(f"removing {repr(pni_file)}")
-            os.unlink(pni_file)
-
-    def clean_man(self):
-        man_dir = os.path.join(self.build_base, "man")
-        if os.path.exists(man_dir):
-            remove_tree(man_dir)
-
-    def run(self):
-        if self.all:
-            self.clean_tests()
-            self.clean_docs()
-            self.clean_man()
-
-        clean.run(self)
-
-
-class x_install(install):
-    """install command with extra Portage paths"""
-
-    user_options = install.user_options + [
-        # note: $prefix and $exec_prefix are reserved for Python install
-        ("system-prefix=", None, "Prefix for architecture-independent data"),
-        ("system-exec-prefix=", None, "Prefix for architecture-specific data"),
-        ("bindir=", None, "Install directory for main executables"),
-        ("datarootdir=", None, "Data install root directory"),
-        ("docdir=", None, "Documentation install directory"),
-        ("htmldir=", None, "HTML documentation install directory"),
-        ("mandir=", None, "Manpage root install directory"),
-        ("portage-base=", "b", "Portage install base"),
-        (
-            "portage-bindir=",
-            None,
-            "Install directory for Portage internal-use executables",
-        ),
-        ("portage-datadir=", None, "Install directory for data files"),
-        ("sbindir=", None, "Install directory for superuser-intended 
executables"),
-        ("sysconfdir=", None, "System configuration path"),
-    ]
-
-    # note: the order is important for proper substitution
-    paths = [
-        ("system_prefix", "/usr"),
-        ("system_exec_prefix", "$system_prefix"),
-        ("bindir", "$system_exec_prefix/bin"),
-        ("sbindir", "$system_exec_prefix/sbin"),
-        ("sysconfdir", "/etc"),
-        ("datarootdir", "$system_prefix/share"),
-        ("docdir", "$datarootdir/doc/$package-$version"),
-        ("htmldir", "$docdir/html"),
-        ("mandir", "$datarootdir/man"),
-        ("portage_base", "$system_exec_prefix/lib/portage"),
-        ("portage_bindir", "$portage_base/bin"),
-        ("portage_datadir", "$datarootdir/portage"),
-        # not customized at the moment
-        ("logrotatedir", "$sysconfdir/logrotate.d"),
-        ("portage_confdir", "$portage_datadir/config"),
-        ("portage_setsdir", "$portage_confdir/sets"),
-    ]
-
-    def initialize_options(self):
-        install.initialize_options(self)
-
-        for key, default in self.paths:
-            setattr(self, key, default)
-        self.subst_paths = {}
-
-    def finalize_options(self):
-        install.finalize_options(self)
-
-        # substitute variables
-        new_paths = {
-            "package": self.distribution.get_name(),
-            "version": self.distribution.get_version(),
-        }
-        for key, _default in self.paths:
-            new_paths[key] = subst_vars(getattr(self, key), new_paths)
-            setattr(self, key, new_paths[key])
-        self.subst_paths = new_paths
-
-
-class x_install_data(install_data):
-    """install_data with customized path support"""
-
-    user_options = install_data.user_options
-
-    def initialize_options(self):
-        install_data.initialize_options(self)
-        self.build_base = None
-        self.paths = None
-
-    def finalize_options(self):
-        install_data.finalize_options(self)
-        self.set_undefined_options("build", ("build_base", "build_base"))
-        self.set_undefined_options("install", ("subst_paths", "paths"))
-
-    def run(self):
-        def re_sub_file(path, pattern, repl):
-            print(f"Rewriting {path}")
-            with codecs.open(path, "r", "utf-8") as f:
-                data = f.read()
-            data = re.sub(pattern, repl, data, flags=re.MULTILINE)
-            with codecs.open(path, "w", "utf-8") as f:
-                f.write(data)
-
-        if create_entry_points:
-            re_sub_file("cnf/repos.conf", r"= /", "= %(EPREFIX)s/")
-            re_sub_file("cnf/make.globals", r'DIR="/', 'DIR="${EPREFIX}/')
-
-        self.run_command("build_man")
-
-        def process_data_files(df):
-            for d, files in df:
-                # substitute man sources
-                if d.startswith("$mandir/"):
-                    files = [os.path.join(self.build_base, v) for v in files]
-
-                # substitute variables in path
-                d = subst_vars(d, self.paths)
-                yield (d, files)
-
-        old_data_files = self.data_files
-        self.data_files = process_data_files(self.data_files)
-
-        install_data.run(self)
-        self.data_files = old_data_files
-
-
-class x_install_lib(install_lib):
-    """install_lib command with Portage path substitution"""
-
-    user_options = install_lib.user_options
-
-    def initialize_options(self):
-        install_lib.initialize_options(self)
-        self.portage_base = None
-        self.portage_bindir = None
-        self.portage_confdir = None
-
-    def finalize_options(self):
-        install_lib.finalize_options(self)
-        self.set_undefined_options(
-            "install",
-            ("portage_base", "portage_base"),
-            ("portage_bindir", "portage_bindir"),
-            ("portage_confdir", "portage_confdir"),
-        )
-
-    def install(self):
-        ret = install_lib.install(self)
-
-        def rewrite_file(path, val_dict):
-            path = os.path.join(self.install_dir, path)
-            print(f"Rewriting {path}")
-            with codecs.open(path, "r", "utf-8") as f:
-                data = f.read()
-
-            for varname, val in val_dict.items():
-                regexp = r"(?m)^(%s\s*=).*$" % varname
-                repl = r"\1 %s" % repr(val)
-
-                data = re.sub(regexp, repl, data)
-
-            with codecs.open(path, "w", "utf-8") as f:
-                f.write(data)
-
-        rewrite_file(
-            "portage/__init__.py",
-            {
-                "VERSION": self.distribution.get_version(),
-            },
-        )
-
-        def re_sub_file(path, pattern_repl_items):
-            path = os.path.join(self.install_dir, path)
-            print(f"Rewriting {path}")
-            with codecs.open(path, "r", "utf-8") as f:
-                data = f.read()
-            for pattern, repl in pattern_repl_items:
-                data = re.sub(pattern, repl, data, flags=re.MULTILINE)
-            with codecs.open(path, "w", "utf-8") as f:
-                f.write(data)
-
-        val_dict = {}
-        if create_entry_points:
-            re_sub_file(
-                "portage/const.py",
-                (
-                    (
-                        r"^(GLOBAL_CONFIG_PATH\s*=\s*[\"'])(.*)([\"'])",
-                        lambda m: "{}{}{}".format(
-                            m.group(1),
-                            m.group(2).partition("/usr")[-1],
-                            m.group(3),
-                        ),
-                    ),
-                    (
-                        r"^(PORTAGE_BASE_PATH\s*=\s*)(.*)",
-                        lambda m: "{}{}".format(
-                            m.group(1),
-                            
'os.path.join(os.path.realpath(__import__("sys").prefix), "lib/portage")',
-                        ),
-                    ),
-                    (
-                        r"^(EPREFIX\s*=\s*)(.*)",
-                        lambda m: f'{m.group(1)}__import__("sys").prefix',
-                    ),
-                ),
-            )
-        else:
-            val_dict.update(
-                {
-                    "PORTAGE_BASE_PATH": self.portage_base,
-                    "PORTAGE_BIN_PATH": self.portage_bindir,
-                }
-            )
-        rewrite_file("portage/const.py", val_dict)
-
-        return ret
-
-
-class x_install_scripts_custom(install_scripts):
-    def initialize_options(self):
-        install_scripts.initialize_options(self)
-        self.root = None
-
-    def finalize_options(self):
-        self.set_undefined_options(
-            "install", ("root", "root"), (self.var_name, "install_dir")
-        )
-        install_scripts.finalize_options(self)
-        self.build_dir = os.path.join(self.build_dir, self.dir_name)
-
-        # prepend root
-        if self.root is not None:
-            self.install_dir = change_root(self.root, self.install_dir)
-
-    def run(self):
-        if not create_entry_points:
-            install_scripts.run(self)
-
-
-class x_install_scripts_bin(x_install_scripts_custom):
-    dir_name = "bin"
-    var_name = "bindir"
-
-
-class x_install_scripts_sbin(x_install_scripts_custom):
-    dir_name = "sbin"
-    var_name = "sbindir"
-
-
-class x_install_scripts_portagebin(x_install_scripts_custom):
-    dir_name = "portage"
-    var_name = "portage_bindir"
-
-
-class x_install_scripts(install_scripts):
-    def initialize_option(self):
-        pass
-
-    def finalize_options(self):
-        pass
-
-    def run(self):
-        self.run_command("install_scripts_bin")
-        self.run_command("install_scripts_portagebin")
-        self.run_command("install_scripts_sbin")
-
-
-class x_sdist(sdist):
-    """sdist defaulting to .tar.bz2 format, and archive files owned by root"""
-
-    def finalize_options(self):
-        if self.owner is None:
-            self.owner = "root"
-        if self.group is None:
-            self.group = "root"
-
-        sdist.finalize_options(self)
-
-
-class build_tests(x_build_scripts_custom):
-    """Prepare build dir for running tests."""
-
-    def initialize_options(self):
-        x_build_scripts_custom.initialize_options(self)
-        self.build_base = None
-        self.build_lib = None
-
-    def finalize_options(self):
-        x_build_scripts_custom.finalize_options(self)
-        self.set_undefined_options(
-            "build", ("build_base", "build_base"), ("build_lib", "build_lib")
-        )
-
-        # since we will be writing to $build_lib/.., it is important
-        # that we do not leave $build_base
-        self.top_dir = os.path.normpath(os.path.join(self.build_lib, ".."))
-        cprefix = os.path.commonprefix((self.build_base, self.top_dir))
-        if cprefix != self.build_base:
-            raise SystemError("build_lib must be a subdirectory of build_base")
-
-        self.build_dir = os.path.join(self.top_dir, "bin")
-
-    def run(self):
-        self.run_command("build_py")
-
-        # install all scripts $build_lib/../bin
-        # (we can't do a symlink since we want shebangs corrected)
-        x_build_scripts_custom.run(self)
-
-        # symlink 'cnf' directory
-        conf_dir = os.path.join(self.top_dir, "cnf")
-        if os.path.exists(conf_dir):
-            if not os.path.islink(conf_dir):
-                raise SystemError(
-                    f"{repr(conf_dir)} exists and is not a symlink (collision)"
-                )
-            os.unlink(conf_dir)
-        conf_src = os.path.relpath("cnf", self.top_dir)
-        print(f"Symlinking {conf_dir} -> {conf_src}")
-        os.symlink(conf_src, conf_dir)
-
-        source_path = os.path.realpath(__file__)
-
-        # copy GPG test keys
-        copy_tree(
-            os.path.join(
-                os.path.dirname(source_path), "lib", "portage", "tests", 
".gnupg"
-            ),
-            os.path.join(self.build_lib, "portage", "tests", ".gnupg"),
-        )
-        os.chmod(os.path.join(self.build_lib, "portage", "tests", ".gnupg"), 
0o700)
-
-        # create $build_lib/../.portage_not_installed
-        # to enable proper paths in tests
-        with open(os.path.join(self.top_dir, ".portage_not_installed"), "w"):
-            pass
-
-
-class test(Command):
-    """run tests"""
-
-    user_options = []
-
-    def initialize_options(self):
-        self.build_lib = None
-
-    def finalize_options(self):
-        self.set_undefined_options("build", ("build_lib", "build_lib"))
-
-    def run(self):
-        self.run_command("build_tests")
-
-        subprocess.check_call(
-            [
-                sys.executable,
-                "-bWd",
-                os.path.join(self.build_lib, "portage/tests/runTests.py"),
-            ]
-        )
-
-
-def find_packages():
-    for dirpath, _dirnames, filenames in os.walk("lib"):
-        if "__init__.py" in filenames:
-            yield os.path.relpath(dirpath, "lib")
-
-
-def find_scripts():
-    for dirpath, _dirnames, filenames in os.walk("bin"):
-        for f in filenames:
-            yield os.path.join(dirpath, f)
-
-
-def get_manpages():
-    linguas = os.environ.get("LINGUAS")
-    if linguas is not None:
-        linguas = linguas.split()
-
-    for dirpath, _dirnames, filenames in os.walk("man"):
-        groups = collections.defaultdict(list)
-        for f in filenames:
-            _fn, suffix = f.rsplit(".", 1)
-            groups[suffix].append(os.path.join(dirpath, f))
-
-        topdir = dirpath[len("man/") :]
-        if not topdir or linguas is None or topdir in linguas:
-            for g, mans in groups.items():
-                yield [os.path.join("$mandir", topdir, f"man{g}"), mans]
-
-
-class build_ext(_build_ext):
-    user_options = _build_ext.user_options + [
-        (
-            "portage-ext-modules",
-            None,
-            "enable portage's C/C++ extensions (cross-compiling is not 
supported)",
-        ),
-    ]
-
-    boolean_options = _build_ext.boolean_options + [
-        "portage_ext_modules",
-    ]
-
-    def initialize_options(self):
-        _build_ext.initialize_options(self)
-        self.portage_ext_modules = None
-
-    def run(self):
-        if self.portage_ext_modules:
-            _build_ext.run(self)
-
-
-def venv_data_files(locations):
-    if not create_entry_points:
-        return
-    for dest_prefix, source_path, file_args in locations:
-        specific_files = []
-        mode_arg = None
-        for arg in file_args:
-            if arg.startswith("-m"):
-                mode_arg = int(arg[2:], 8)
-            else:
-                specific_files.append(arg)
-
-        abs_source_path = os.path.abspath(source_path)
-        for root, dirs, files in os.walk(abs_source_path):
-            root_offset = root[len(abs_source_path) :].lstrip("/")
-            dest_path = os.path.join(dest_prefix, root_offset)
-
-            if specific_files:
-                matched_files = list(
-                    itertools.chain.from_iterable(
-                        glob.glob(os.path.join(root, x)) for x in 
specific_files
-                    )
-                )
-            else:
-                matched_files = [os.path.join(root, x) for x in files]
-
-            if mode_arg:
-                for filename in matched_files:
-                    if not os.path.islink(filename):
-                        os.chmod(filename, mode_arg)
-
-            yield (dest_path, matched_files)
-
-
-def get_data_files(regular_files, venv_files):
-    if create_entry_points:
-        return list(venv_data_files(venv_files))
-
-    return regular_files
-
-
-setup(
-    name="portage",
-    version="3.0.49",
-    url="https://wiki.gentoo.org/wiki/Project:Portage";,
-    project_urls={
-        "Release Notes": 
"https://gitweb.gentoo.org/proj/portage.git/plain/NEWS";,
-        "Documentation": 
"https://wiki.gentoo.org/wiki/Handbook:AMD64/Working/Portage";,
-    },
-    author="Gentoo Portage Development Team",
-    author_email="dev-port...@gentoo.org",
-    description="Portage is the package management and distribution system for 
Gentoo",
-    license="GPLV2",
-    long_description=long_description,
-    long_description_content_type="text/markdown",
-    package_dir={"": "lib"},
-    packages=list(find_packages()),
-    # something to cheat build & install commands
-    scripts=list(find_scripts()),
-    data_files=get_data_files(
-        list(get_manpages())
-        + [
-            ["$sysconfdir", ["cnf/etc-update.conf", "cnf/dispatch-conf.conf"]],
-            ["$logrotatedir", ["cnf/logrotate.d/elog-save-summary"]],
-            [
-                "$portage_confdir",
-                ["cnf/make.conf.example", "cnf/make.globals", 
"cnf/repos.conf"],
-            ],
-            ["$portage_setsdir", ["cnf/sets/portage.conf"]],
-            ["$docdir", ["NEWS", "RELEASE-NOTES"]],
-            ["$portage_confdir/repo.postsync.d", 
["cnf/repo.postsync.d/example"]],
-        ],
-        [
-            ("etc", "cnf", ("etc-update.conf", "dispatch-conf.conf")),
-            ("etc/logrotate.d", "cnf/logrotate.d", ("elog-save-summary",)),
-            (
-                "share/portage/config/repo.postsync.d",
-                "cnf/repo.postsync.d",
-                ("example",),
-            ),
-            (
-                "share/portage/config",
-                "cnf",
-                ("make.conf.example", "make.globals", "repos.conf"),
-            ),
-            ("share/portage/config/sets", "cnf/sets", ("*.conf",)),
-            ("share/man/man1", "man", ("*.1",)),
-            ("share/man/man5", "man", ("*.5",)),
-            ("share/portage/doc", "", ("NEWS", "RELEASE-NOTES")),
-            ("lib/portage/bin", "bin", ("-m0755",)),
-        ],
-    ),
-    entry_points={
-        "console_scripts": [
-            
f"{os.path.basename(path)}=portage.util.bin_entry_point:bin_entry_point"
-            for path in itertools.chain.from_iterable(x_scripts.values())
-        ],
-    }
-    if create_entry_points
-    else {},
-    # create_entry_points disables ext_modules, for pure python
-    ext_modules=[]
-    if create_entry_points
-    else [
-        Extension(
-            name=n,
-            sources=m,
-            extra_compile_args=[
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE_SOURCE",
-                "-D_LARGEFILE64_SOURCE",
-            ],
-        )
-        for n, m in x_c_helpers.items()
-    ],
-    cmdclass={
-        "build": x_build,
-        "build_ext": build_ext,
-        "build_man": build_man,
-        "build_scripts": x_build_scripts,
-        "build_scripts_bin": x_build_scripts_bin,
-        "build_scripts_portagebin": x_build_scripts_portagebin,
-        "build_scripts_sbin": x_build_scripts_sbin,
-        "build_tests": build_tests,
-        "clean": x_clean,
-        "docbook": docbook,
-        "apidoc": apidoc,
-        "install": x_install,
-        "install_data": x_install_data,
-        "install_docbook": install_docbook,
-        "install_apidoc": install_apidoc,
-        "install_lib": x_install_lib,
-        "install_scripts": x_install_scripts,
-        "install_scripts_bin": x_install_scripts_bin,
-        "install_scripts_portagebin": x_install_scripts_portagebin,
-        "install_scripts_sbin": x_install_scripts_sbin,
-        "sdist": x_sdist,
-        "test": test,
-    },
-    classifiers=[
-        "Development Status :: 5 - Production/Stable",
-        "Environment :: Console",
-        "Intended Audience :: System Administrators",
-        "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
-        "Operating System :: POSIX",
-        "Programming Language :: Python :: 3",
-        "Topic :: System :: Installation/Setup",
-    ],
-    python_requires=">=3.9",
-)

diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 000000000..cbc7aa611
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,50 @@
+# We create symlinks to the native extensions in the source tree for the tests
+# and for development. Meson does not allow you to build in-place and Python
+# cannot create a single namespace from two identically-named paths.
+
+libc_ext = py.extension_module(
+    'libc',
+    'portage_util_libc.c',
+    dependencies : py.dependency(),
+    subdir : 'portage' / 'util',
+    install : true
+)
+
+whirlpool_ext = py.extension_module(
+    '_whirlpool',
+    'portage_util__whirlpool.c',
+    dependencies : py.dependency(),
+    subdir : 'portage' / 'util',
+    install : true
+)
+
+run_command(
+    [
+        'ln', '-srnf',
+        libc_ext.full_path(),
+        whirlpool_ext.full_path(),
+        meson.project_source_root() / 'lib' / 'portage' / 'util/'
+    ],
+    capture : false,
+    check : true
+)
+
+if host_machine.system() == 'linux'
+    reflink_ext = py.extension_module(
+        'reflink_linux',
+        'portage_util_file_copy_reflink_linux.c',
+        dependencies : py.dependency(),
+        subdir : 'portage' / 'util' / 'file_copy',
+        install : true
+    )
+
+    run_command(
+        [
+            'ln', '-srnf',
+            reflink_ext.full_path(),
+            meson.project_source_root() / 'lib' / 'portage' / 'util' / 
'file_copy/'
+        ],
+        capture : false,
+        check : true
+    )
+endif

diff --git a/tox.ini b/tox.ini
index 4ea388e35..6a52a8a0a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -28,4 +28,4 @@ allowlist_externals =
        ./run-pylint
 commands =
        pylint: ./run-pylint
-       test: python -b -Wd setup.py test
+       test: python -bWd lib/portage/tests/runTests.py

Reply via email to