Repository: espressif/pytest-embedded (language: python)

Issue:
[Release] Use pyproject.toml instead of setup.py (RDT-461)
Got a few things to fix
- [x] Metadatas
  - [x] More classifiers. Shows the status of the packages
  - [x] Homepage Web URL, documentation Web URL
- [x] `pip install -e .` will lead to an error: `cannot import name 'Dut' from 'pytest_embedded' (unknown location)`
- [x] use `flit` to generate the `setup.py` for the legacy version users.



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml
index c6e04fa0..90d81727 100644
--- a/.github/workflows/publish_pypi.yml
+++ b/.github/workflows/publish_pypi.yml
@@ -14,15 +14,12 @@ jobs:
         uses: actions/setup-python@v4
         with:
           python-version: '3.7'
-      - name: Install dependencies
+      - name: Build packages
         run: |
           python -m pip install --upgrade pip
-          pip install wheel
-          pip install twine
-      - name: Build packages
-        run: bash foreach.sh build
+          bash foreach.sh build
       - name: Publish packages
         env:
-          TWINE_USERNAME: __token__
-          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
+          FLIT_USERNAME: __token__
+          FLIT_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
         run: bash foreach.sh publish
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f2152648..ca31f302 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -19,7 +19,6 @@ By default, all tests under all plugins would be run.
 ```shell
 $ pip install -r requirements.txt
 $ bash foreach.sh install
-$ # export DONT_SKIP_QEMU_TESTS=1 (when qemu-system-xtensa is ready)
 $ # export DONT_SKIP_JTAG_TESTS=1 (when you have a jtag connection)
 $ pytest
 ```
@@ -32,13 +31,23 @@ refer to the tests under each plugin.
 
 ## Building Docs
 
-We use `mkdocs` and `mkdocstring` with theme `mkdocs-material` to build docs.
+We use `sphinx` with espressif theme to build the docs. The docstring is written in `Google` style. Here's an [example](https://www.sphinx-doc.org/en/master/usage/extensions/example_google.html).
 
-### Test Docs Locally
+For building locally, you need to install the dependencies first.
+
+```shell
+$ pip install -r docs/requirements.txt
+```
+
+Then you can build the docs by
 
 ```shell
 $ cd docs
-$ pip install -r requirements.txt
-$ mkdocs serve # For preview
-$ mkdocs build # For build
+$ make html
+```
+
+For documentation preview, you may use any browser you prefer. The executable has to be searchable in `PATH`. For example we're using firefox here.
+
+```shell
+$ firefox _build/html/index.html
 ```
diff --git a/foreach.sh b/foreach.sh
index f001c2a5..504097aa 100755
--- a/foreach.sh
+++ b/foreach.sh
@@ -15,25 +15,20 @@ DEFAULT_PACKAGES=" \
 action=${1:-"install"}
 res=0
 
+pip install flit
+
 for pkg in $DEFAULT_PACKAGES; do
   pushd "$pkg"
   if [ "$action" = "install" ]; then
-    rm -rf ./build
-    if [ "$pkg" = "pytest-embedded-idf" ]; then
-      pip install -e ".[serial]"
-    else
-      pip install -e .
-    fi
+    flit install -s
   elif [ "$action" = "uninstall" ]; then
     pip uninstall -y $pkg
   elif [ "$action" = "build" ]; then
-    python setup.py sdist bdist_wheel
+    flit build
   elif [ "$action" = "publish" ]; then
-    python -m twine upload --verbose dist/* || res=1
-  elif [ "$action" = "check" ]; then
-    twine check dist/* --strict
+    flit upload
   else
-    echo "invalid argument. valid choices: install/uninstall/build/publish/check"
+    echo "invalid argument. valid choices: install/uninstall/build/publish"
     exit 1
   fi
   popd
diff --git a/mkdocs.yml b/mkdocs.yml
deleted file mode 100644
index 6910102b..00000000
--- a/mkdocs.yml
+++ /dev/null
@@ -1,72 +0,0 @@
-site_name: pytest-embedded
-
-theme:
-  name: "material"
-  features:
-    - toc.integrate
-  palette:
-    - scheme: default
-      toggle:
-        icon: material/toggle-switch-off-outline
-        name: Switch to dark mode
-    - scheme: slate
-      toggle:
-        icon: material/toggle-switch
-        name: Switch to light mode
-
-watch:
-  - pytest-embedded
-  - pytest-embedded-arduino
-  - pytest-embedded-idf
-  - pytest-embedded-jtag
-  - pytest-embedded-qemu
-  - pytest-embedded-serial
-  - pytest-embedded-serial-esp
-
-markdown_extensions:
-  # admonitions
-  - admonition
-  - pymdownx.details
-  - pymdownx.superfences
-  # highlight:
-  - pymdownx.highlight:
-      anchor_linenums: true
-  - pymdownx.inlinehilite
-  - pymdownx.snippets:
-      check_paths: true
-  - pymdownx.superfences:
-      custom_fences:
-        - name: mermaid
-          class: mermaid
-          format: !!python/name:pymdownx.superfences.fence_code_format
-  # tabbed
-  - pymdownx.tabbed:
-      alternate_style: true
-  # abbreviations
-  - abbr
-  - pymdownx.snippets
-
-plugins:
-  - autorefs
-  - search
-  - mkdocstrings:
-      handlers:
-        python:
-          options:
-            show_root_heading: True
-
-nav:
-  - pytest-embedded: index.md
-  - key_concepts.md
-  - services.md
-  - expecting.md
-  - CONTRIBUTING.md
-  - CHANGELOG.md
-  - References:
-    - references/pytest_embedded.md
-    - references/pytest_embedded_serial.md
-    - references/pytest_embedded_serial_esp.md
-    - references/pytest_embedded_jtag.md
-    - references/pytest_embedded_idf.md
-    - references/pytest_embedded_arduino.md
-    - references/pytest_embedded_qemu.md
diff --git a/pyproject.toml b/pyproject.toml
index b626f1f3..c9197f51 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,6 +2,22 @@
 name = "cz_conventional_commits"
 version = "1.3.1"
 tag_format = "v$version"
+version_files = [
+    "pytest-embedded/pytest_embedded/__init__.py",
+    "pytest-embedded/pyproject.toml",
+    "pytest-embedded-arduino/pytest_embedded_arduino/__init__.py",
+    "pytest-embedded-arduino/pyproject.toml",
+    "pytest-embedded-idf/pytest_embedded_idf/__init__.py",
+    "pytest-embedded-idf/pyproject.toml",
+    "pytest-embedded-jtag/pytest_embedded_jtag/__init__.py",
+    "pytest-embedded-jtag/pyproject.toml",
+    "pytest-embedded-qemu/pytest_embedded_qemu/__init__.py",
+    "pytest-embedded-qemu/pyproject.toml",
+    "pytest-embedded-serial/pytest_embedded_serial/__init__.py",
+    "pytest-embedded-serial/pyproject.toml",
+    "pytest-embedded-serial-esp/pytest_embedded_serial_esp/__init__.py",
+    "pytest-embedded-serial-esp/pyproject.toml",
+]
 
 [tool.isort]
 profile = 'black'
diff --git a/requirements.txt b/requirements.txt
index 53fbc202..d9f07ade 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,9 @@
 # This is a requirements.txt for extra dependencies.
 # To install all packages, please run `bash foreach.sh install`
 
+# build
+flit
+
 # testing
 pytest-cov
 pytest-rerunfailures


Test patch:
diff --git a/.github/workflows/test-build-docs.yml b/.github/workflows/test-build-docs.yml
index 4e619c32..09869405 100644
--- a/.github/workflows/test-build-docs.yml
+++ b/.github/workflows/test-build-docs.yml
@@ -7,6 +7,9 @@ on:
       - 'docs/**'
       - '*.md'
 
+env:
+  FLIT_ROOT_INSTALL: 1
+
 jobs:
   test-build-docs:
     runs-on: ubuntu-latest
@@ -19,7 +22,6 @@ jobs:
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip
-          pip install -r requirements.txt
           bash foreach.sh install
           pip install -r docs/requirements.txt
       - name: Test build docs
diff --git a/.github/workflows/test-build-package.yml b/.github/workflows/test-build-package.yml
index 204e6640..6b96c924 100644
--- a/.github/workflows/test-build-package.yml
+++ b/.github/workflows/test-build-package.yml
@@ -12,11 +12,7 @@ jobs:
         uses: actions/setup-python@v4
         with:
           python-version: '3.7'
-      - name: Install dependencies
+      - name: Build packages
         run: |
           python -m pip install --upgrade pip
-          pip install wheel twine
-      - name: Build packages
-        run: bash foreach.sh build
-      - name: Twine check
-        run: bash foreach.sh check
+          bash foreach.sh build
diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml
index 15ea3b10..1ddd2709 100644
--- a/.github/workflows/test-python.yml
+++ b/.github/workflows/test-python.yml
@@ -7,6 +7,9 @@ on:
   push:
     branches: [ main ]
 
+env:
+  FLIT_ROOT_INSTALL: 1
+
 jobs:
   test-python:
     timeout-minutes: 30
diff --git a/conftest.py b/conftest.py
index 52dabddd..c58c98d4 100644
--- a/conftest.py
+++ b/conftest.py
@@ -1,10 +1,8 @@
 import os
 import sys
-from copy import deepcopy
 from distutils.dir_util import copy_tree
 from typing import List, Pattern
 
-import pkg_resources
 import pytest
 from _pytest.config import Config
 from _pytest.fixtures import FixtureRequest
@@ -43,11 +41,6 @@ def temp_disable_packages(monkeypatch, request: FixtureRequest):
         if name in packages or name.split('.')[0] in packages:
             monkeypatch.setitem(sys.modules, name, None)
 
-    deepcopy_sys_path = deepcopy(sys.path)
-    for package in packages:
-        deepcopy_sys_path.remove(pkg_resources.get_distribution(package).location)
-    monkeypatch.setattr(sys, 'path', deepcopy_sys_path)
-
 
 def pytest_configure(config: Config) -> None:
     for name, description in {'temp_disable_packages': 'disable packages in function scope'}.items():
diff --git a/LICENSE b/pytest-embedded-arduino/LICENSE
similarity index 94%
rename from LICENSE
rename to pytest-embedded-arduino/LICENSE
index 68c54ea8..d73ab57c 100644
--- a/LICENSE
+++ b/pytest-embedded-arduino/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2021 Espressif Systems (Shanghai) Co. Ltd.
+Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/pytest-embedded-arduino/pyproject.toml b/pytest-embedded-arduino/pyproject.toml
new file mode 100644
index 00000000..d0d0b54b
--- /dev/null
+++ b/pytest-embedded-arduino/pyproject.toml
@@ -0,0 +1,45 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "pytest-embedded-arduino"
+authors = [
+    {name = "Abdelatif Guettouche", email = "abdelatif.guettouche@espressif.com"},
+    {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
+]
+readme = "README.md"
+license = {file = "LICENSE"}
+classifiers = [
+    "Development Status :: 5 - Production/Stable",
+    "Framework :: Pytest",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: MIT License",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python",
+    "Topic :: Software Development :: Testing",
+]
+dynamic = ["version", "description"]
+requires-python = ">=3.7"
+
+dependencies = [
+    "pytest-embedded~=1.3.1",
+]
+
+[project.optional-dependencies]
+serial = [
+    "pytest-embedded-serial-esp~=1.3.1"
+]
+
+[project.urls]
+homepage = "https://github.com/espressif/pytest-embedded"
+repository = "https://github.com/espressif/pytest-embedded"
+documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
+changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
diff --git a/pytest-embedded-arduino/pytest_embedded_arduino/__init__.py b/pytest-embedded-arduino/pytest_embedded_arduino/__init__.py
index a73ec5d9..3fb20a04 100644
--- a/pytest-embedded-arduino/pytest_embedded_arduino/__init__.py
+++ b/pytest-embedded-arduino/pytest_embedded_arduino/__init__.py
@@ -1,3 +1,5 @@
+"""Make pytest-embedded plugin work with Arduino."""
+
 from .app import ArduinoApp
 from .serial import ArduinoSerial
 
@@ -5,3 +7,5 @@
     'ArduinoApp',
     'ArduinoSerial',
 ]
+
+__version__ = '1.3.1'
diff --git a/pytest-embedded-arduino/setup.py b/pytest-embedded-arduino/setup.py
deleted file mode 100644
index 24b27587..00000000
--- a/pytest-embedded-arduino/setup.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import codecs
-import os
-import re
-
-import setuptools
-from setuptools import setup
-
-
-def read(fname):
-    file_path = os.path.join(os.path.dirname(__file__), fname)
-    return codecs.open(file_path, encoding='utf-8').read()
-
-
-def get_version():
-    regex = re.compile(r'version = "(.+)"')
-    return regex.findall(read('../pyproject.toml'))[0]
-
-
-AUTHOR = 'Abdelatif Guettouche'
-EMAIL = 'abdelatif.guettouche@espressif.com'
-NAME = 'pytest-embedded-arduino'
-SHORT_DESCRIPTION = 'pytest embedded plugin for Arduino projects'
-LICENSE = 'MIT'
-URL = 'https://docs.espressif.com/projects/pytest-embedded/en/latest/'
-VERSION = get_version()
-REQUIRES = [
-    f'pytest-embedded~={VERSION}',
-]
-EXTRAS_REQUIRE = {
-    'serial': f'pytest-embedded-serial-esp~={VERSION}',
-}
-ENTRY_POINTS = {}
-
-setup(
-    name=NAME,
-    version=VERSION,
-    author=AUTHOR,
-    author_email=EMAIL,
-    license=LICENSE,
-    url=URL,
-    description=SHORT_DESCRIPTION,
-    long_description=read('README.md'),
-    long_description_content_type='text/markdown',
-    packages=setuptools.find_packages(exclude='tests'),
-    python_requires='>=3.7',
-    install_requires=REQUIRES,
-    extras_require=EXTRAS_REQUIRE,
-    classifiers=[
-        'Framework :: Pytest',
-        'Intended Audience :: Developers',
-        'Topic :: Software Development :: Testing',
-        'Development Status :: 5 - Production/Stable',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Programming Language :: Python :: 3.10',
-        'Programming Language :: Python :: 3.11',
-        'Programming Language :: Python :: 3 :: Only',
-        'Operating System :: OS Independent',
-        'License :: OSI Approved :: MIT License',
-    ],
-    entry_points=ENTRY_POINTS,
-)
diff --git a/pytest-embedded-idf/LICENSE b/pytest-embedded-idf/LICENSE
new file mode 100644
index 00000000..d73ab57c
--- /dev/null
+++ b/pytest-embedded-idf/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/pytest-embedded-idf/pyproject.toml b/pytest-embedded-idf/pyproject.toml
new file mode 100644
index 00000000..75734901
--- /dev/null
+++ b/pytest-embedded-idf/pyproject.toml
@@ -0,0 +1,45 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "pytest-embedded-idf"
+authors = [
+    {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
+]
+readme = "README.md"
+license = {file = "LICENSE"}
+classifiers = [
+    "Development Status :: 5 - Production/Stable",
+    "Framework :: Pytest",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: MIT License",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python",
+    "Topic :: Software Development :: Testing",
+]
+dynamic = ["version", "description"]
+requires-python = ">=3.7"
+
+dependencies = [
+    "pytest-embedded~=1.3.1",
+]
+
+[project.optional-dependencies]
+serial = [
+    "pytest-embedded-serial-esp~=1.3.1",
+    "esp-coredump~=1.0",
+]
+
+[project.urls]
+homepage = "https://github.com/espressif/pytest-embedded"
+repository = "https://github.com/espressif/pytest-embedded"
+documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
+changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
diff --git a/pytest-embedded-idf/pytest_embedded_idf/__init__.py b/pytest-embedded-idf/pytest_embedded_idf/__init__.py
index 05689e7f..8be467a6 100644
--- a/pytest-embedded-idf/pytest_embedded_idf/__init__.py
+++ b/pytest-embedded-idf/pytest_embedded_idf/__init__.py
@@ -1,3 +1,5 @@
+"""Make pytest-embedded plugin work with ESP-IDF."""
+
 import importlib
 
 from pytest_embedded.utils import lazy_load
@@ -30,3 +32,5 @@
     'LinuxDut',
     'UnittestMenuCase',
 ]
+
+__version__ = '1.3.1'
diff --git a/pytest-embedded-idf/setup.py b/pytest-embedded-idf/setup.py
deleted file mode 100644
index 1dc8f4d2..00000000
--- a/pytest-embedded-idf/setup.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import codecs
-import os
-import re
-
-import setuptools
-from setuptools import setup
-
-
-def read(fname):
-    file_path = os.path.join(os.path.dirname(__file__), fname)
-    return codecs.open(file_path, encoding='utf-8').read()
-
-
-def get_version():
-    regex = re.compile(r'version = "(.+)"')
-    return regex.findall(read(os.path.join('..', 'pyproject.toml')))[0]
-
-
-AUTHOR = 'Fu Hanxi'
-EMAIL = 'fuhanxi@espressif.com'
-NAME = 'pytest-embedded-idf'
-SHORT_DESCRIPTION = 'pytest embedded plugin for esp-idf project'
-LICENSE = 'MIT'
-URL = 'https://docs.espressif.com/projects/pytest-embedded/en/latest/'
-VERSION = get_version()
-REQUIRES = [
-    f'pytest-embedded~={VERSION}',
-]
-EXTRAS_REQUIRE = {
-    'serial': [f'pytest-embedded-serial-esp~={VERSION}', 'esp-coredump~=1.0'],
-}
-ENTRY_POINTS = {}
-
-setup(
-    name=NAME,
-    version=VERSION,
-    author=AUTHOR,
-    author_email=EMAIL,
-    license=LICENSE,
-    url=URL,
-    description=SHORT_DESCRIPTION,
-    long_description=read('README.md'),
-    long_description_content_type='text/markdown',
-    packages=setuptools.find_packages(exclude='tests'),
-    python_requires='>=3.7',
-    install_requires=REQUIRES,
-    extras_require=EXTRAS_REQUIRE,
-    classifiers=[
-        'Framework :: Pytest',
-        'Intended Audience :: Developers',
-        'Topic :: Software Development :: Testing',
-        'Development Status :: 5 - Production/Stable',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Programming Language :: Python :: 3.10',
-        'Programming Language :: Python :: 3.11',
-        'Programming Language :: Python :: 3 :: Only',
-        'Operating System :: OS Independent',
-        'License :: OSI Approved :: MIT License',
-    ],
-    entry_points=ENTRY_POINTS,
-)
diff --git a/pytest-embedded-idf/tests/test_idf.py b/pytest-embedded-idf/tests/test_idf.py
index 0fb9caf7..b98ac53a 100644
--- a/pytest-embedded-idf/tests/test_idf.py
+++ b/pytest-embedded-idf/tests/test_idf.py
@@ -362,7 +362,12 @@ def test_detect_port(dut):
 @pytest.mark.temp_disable_packages('pytest_embedded_serial')
 def test_hello_world_linux(testdir):
     testdir.makepyfile(r"""
+        import pytest
+
         def test_hello_world_linux(dut):
+            with pytest.raises(ImportError):
+                import pytest_embedded_serial
+
             dut.expect('Hello world!')
             dut.expect('Restarting')
     """)
@@ -438,7 +443,7 @@ def test_idf_parse_test_menu():
 \t(1)\t"ledc_cpu_reset_test_first_stage"
 \t(2)\t"ledc_cpu_reset_test_second_stage"
 '''
-    test_menu = IdfDut.parse_unity_menu_from_str(s)
+    test_menu = IdfDut._parse_unity_menu_from_str(s)
 
     assert len(test_menu) == 3
 
diff --git a/pytest-embedded-jtag/LICENSE b/pytest-embedded-jtag/LICENSE
new file mode 100644
index 00000000..d73ab57c
--- /dev/null
+++ b/pytest-embedded-jtag/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/pytest-embedded-jtag/pyproject.toml b/pytest-embedded-jtag/pyproject.toml
new file mode 100644
index 00000000..a4e02243
--- /dev/null
+++ b/pytest-embedded-jtag/pyproject.toml
@@ -0,0 +1,39 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "pytest-embedded-jtag"
+authors = [
+    {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
+]
+readme = "README.md"
+license = {file = "LICENSE"}
+classifiers = [
+    "Development Status :: 5 - Production/Stable",
+    "Framework :: Pytest",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: MIT License",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python",
+    "Topic :: Software Development :: Testing",
+]
+dynamic = ["version", "description"]
+requires-python = ">=3.7"
+
+dependencies = [
+    "pytest-embedded-serial~=1.3.1",
+]
+
+[project.urls]
+homepage = "https://github.com/espressif/pytest-embedded"
+repository = "https://github.com/espressif/pytest-embedded"
+documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
+changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
diff --git a/pytest-embedded-jtag/pytest_embedded_jtag/__init__.py b/pytest-embedded-jtag/pytest_embedded_jtag/__init__.py
index d6e5b665..7ccd8441 100644
--- a/pytest-embedded-jtag/pytest_embedded_jtag/__init__.py
+++ b/pytest-embedded-jtag/pytest_embedded_jtag/__init__.py
@@ -1,3 +1,5 @@
+"""Make pytest-embedded plugin work with JTAG."""
+
 from .gdb import Gdb
 from .openocd import OpenOcd
 
@@ -5,3 +7,5 @@
     'OpenOcd',
     'Gdb',
 ]
+
+__version__ = '1.3.1'
diff --git a/pytest-embedded-jtag/setup.py b/pytest-embedded-jtag/setup.py
deleted file mode 100644
index dfc1e642..00000000
--- a/pytest-embedded-jtag/setup.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import codecs
-import os
-import re
-
-import setuptools
-from setuptools import setup
-
-
-def read(fname):
-    file_path = os.path.join(os.path.dirname(__file__), fname)
-    return codecs.open(file_path, encoding='utf-8').read()
-
-
-def get_version():
-    regex = re.compile(r'version = "(.+)"')
-    return regex.findall(read(os.path.join('..', 'pyproject.toml')))[0]
-
-
-AUTHOR = 'Fu Hanxi'
-EMAIL = 'fuhanxi@espressif.com'
-NAME = 'pytest-embedded-jtag'
-SHORT_DESCRIPTION = 'pytest embedded plugin for testing with jtag'
-LICENSE = 'MIT'
-URL = 'https://docs.espressif.com/projects/pytest-embedded/en/latest/'
-VERSION = get_version()
-REQUIRES = [
-    f'pytest-embedded-serial~={VERSION}',
-]
-ENTRY_POINTS = {}
-
-setup(
-    name=NAME,
-    version=VERSION,
-    author=AUTHOR,
-    author_email=EMAIL,
-    license=LICENSE,
-    url=URL,
-    description=SHORT_DESCRIPTION,
-    long_description=read('README.md'),
-    long_description_content_type='text/markdown',
-    packages=setuptools.find_packages(exclude='tests'),
-    python_requires='>=3.7',
-    install_requires=REQUIRES,
-    classifiers=[
-        'Framework :: Pytest',
-        'Intended Audience :: Developers',
-        'Topic :: Software Development :: Testing',
-        'Development Status :: 5 - Production/Stable',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Programming Language :: Python :: 3.10',
-        'Programming Language :: Python :: 3.11',
-        'Programming Language :: Python :: 3 :: Only',
-        'Operating System :: OS Independent',
-        'License :: OSI Approved :: MIT License',
-    ],
-    entry_points=ENTRY_POINTS,
-)
diff --git a/pytest-embedded-qemu/LICENSE b/pytest-embedded-qemu/LICENSE
new file mode 100644
index 00000000..d73ab57c
--- /dev/null
+++ b/pytest-embedded-qemu/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/pytest-embedded-qemu/pyproject.toml b/pytest-embedded-qemu/pyproject.toml
new file mode 100644
index 00000000..08280b04
--- /dev/null
+++ b/pytest-embedded-qemu/pyproject.toml
@@ -0,0 +1,44 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "pytest-embedded-qemu"
+authors = [
+    {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
+]
+readme = "README.md"
+license = {file = "LICENSE"}
+classifiers = [
+    "Development Status :: 5 - Production/Stable",
+    "Framework :: Pytest",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: MIT License",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python",
+    "Topic :: Software Development :: Testing",
+]
+dynamic = ["version", "description"]
+requires-python = ">=3.7"
+
+dependencies = [
+    "pytest-embedded~=1.3.1",
+]
+
+[project.optional-dependencies]
+idf = [
+    "pytest-embedded-idf~=1.3.1",
+]
+
+[project.urls]
+homepage = "https://github.com/espressif/pytest-embedded"
+repository = "https://github.com/espressif/pytest-embedded"
+documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
+changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
diff --git a/pytest-embedded-qemu/pytest_embedded_qemu/__init__.py b/pytest-embedded-qemu/pytest_embedded_qemu/__init__.py
index e4b42d21..e673a121 100644
--- a/pytest-embedded-qemu/pytest_embedded_qemu/__init__.py
+++ b/pytest-embedded-qemu/pytest_embedded_qemu/__init__.py
@@ -1,3 +1,5 @@
+"""Make pytest-embedded plugin work with QEMU."""
+
 DEFAULT_IMAGE_FN = 'flash_image.bin'
 ENCRYPTED_IMAGE_FN = f'encrypted_{DEFAULT_IMAGE_FN}'
 
@@ -12,3 +14,5 @@
     'QemuApp',
     'QemuDut',
 ]
+
+__version__ = '1.3.1'
diff --git a/pytest-embedded-qemu/setup.py b/pytest-embedded-qemu/setup.py
deleted file mode 100644
index 2be479a0..00000000
--- a/pytest-embedded-qemu/setup.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import codecs
-import os
-import re
-
-import setuptools
-from setuptools import setup
-
-
-def read(fname):
-    file_path = os.path.join(os.path.dirname(__file__), fname)
-    return codecs.open(file_path, encoding='utf-8').read()
-
-
-def get_version():
-    regex = re.compile(r'version = "(.+)"')
-    return regex.findall(read(os.path.join('..', 'pyproject.toml')))[0]
-
-
-AUTHOR = 'Fu Hanxi'
-EMAIL = 'fuhanxi@espressif.com'
-NAME = 'pytest-embedded-qemu'
-SHORT_DESCRIPTION = 'pytest embedded plugin for qemu, not target chip'
-LICENSE = 'MIT'
-URL = 'https://docs.espressif.com/projects/pytest-embedded/en/latest/'
-VERSION = get_version()
-REQUIRES = [
-    f'pytest-embedded~={VERSION}',
-]
-EXTRAS_REQUIRE = {
-    'idf': f'pytest-embedded-idf~={VERSION}',
-}
-ENTRY_POINTS = {}
-
-setup(
-    name=NAME,
-    version=VERSION,
-    author=AUTHOR,
-    author_email=EMAIL,
-    license=LICENSE,
-    url=URL,
-    description=SHORT_DESCRIPTION,
-    long_description=read('README.md'),
-    long_description_content_type='text/markdown',
-    packages=setuptools.find_packages(exclude='tests'),
-    python_requires='>=3.7',
-    install_requires=REQUIRES,
-    extras_require=EXTRAS_REQUIRE,
-    classifiers=[
-        'Framework :: Pytest',
-        'Intended Audience :: Developers',
-        'Topic :: Software Development :: Testing',
-        'Development Status :: 5 - Production/Stable',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Programming Language :: Python :: 3.10',
-        'Programming Language :: Python :: 3.11',
-        'Programming Language :: Python :: 3 :: Only',
-        'Operating System :: OS Independent',
-        'License :: OSI Approved :: MIT License',
-    ],
-    entry_points=ENTRY_POINTS,
-)
diff --git a/pytest-embedded-serial-esp/LICENSE b/pytest-embedded-serial-esp/LICENSE
new file mode 100644
index 00000000..d73ab57c
--- /dev/null
+++ b/pytest-embedded-serial-esp/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/pytest-embedded-serial-esp/pyproject.toml b/pytest-embedded-serial-esp/pyproject.toml
new file mode 100644
index 00000000..57854ba4
--- /dev/null
+++ b/pytest-embedded-serial-esp/pyproject.toml
@@ -0,0 +1,40 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "pytest-embedded-serial-esp"
+authors = [
+    {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
+]
+readme = "README.md"
+license = {file = "LICENSE"}
+classifiers = [
+    "Development Status :: 5 - Production/Stable",
+    "Framework :: Pytest",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: MIT License",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python",
+    "Topic :: Software Development :: Testing",
+]
+dynamic = ["version", "description"]
+requires-python = ">=3.7"
+
+dependencies = [
+    "pytest-embedded-serial~=1.3.1",
+    "esptool~=4.5",
+]
+
+[project.urls]
+homepage = "https://github.com/espressif/pytest-embedded"
+repository = "https://github.com/espressif/pytest-embedded"
+documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
+changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
diff --git a/pytest-embedded-serial-esp/pytest_embedded_serial_esp/__init__.py b/pytest-embedded-serial-esp/pytest_embedded_serial_esp/__init__.py
index 9762bdb1..2b397c50 100644
--- a/pytest-embedded-serial-esp/pytest_embedded_serial_esp/__init__.py
+++ b/pytest-embedded-serial-esp/pytest_embedded_serial_esp/__init__.py
@@ -1,5 +1,9 @@
+"""Make pytest-embedded plugin work with Espressif target boards."""
+
 from .serial import EspSerial
 
 __all__ = [
     'EspSerial',
 ]
+
+__version__ = '1.3.1'
diff --git a/pytest-embedded-serial-esp/setup.py b/pytest-embedded-serial-esp/setup.py
deleted file mode 100644
index 02a932ea..00000000
--- a/pytest-embedded-serial-esp/setup.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import codecs
-import os
-import re
-
-import setuptools
-from setuptools import setup
-
-
-def read(fname):
-    file_path = os.path.join(os.path.dirname(__file__), fname)
-    return codecs.open(file_path, encoding='utf-8').read()
-
-
-def get_version():
-    regex = re.compile(r'version = "(.+)"')
-    return regex.findall(read(os.path.join('..', 'pyproject.toml')))[0]
-
-
-AUTHOR = 'Fu Hanxi'
-EMAIL = 'fuhanxi@espressif.com'
-NAME = 'pytest-embedded-serial-esp'
-SHORT_DESCRIPTION = 'pytest embedded plugin for testing espressif boards via serial ports'
-LICENSE = 'MIT'
-URL = 'https://docs.espressif.com/projects/pytest-embedded/en/latest/'
-VERSION = get_version()
-REQUIRES = [
-    f'pytest-embedded-serial~={VERSION}',
-    'esptool>=4.0',
-]
-ENTRY_POINTS = {}
-
-setup(
-    name=NAME,
-    version=get_version(),
-    author=AUTHOR,
-    author_email=EMAIL,
-    license=LICENSE,
-    url=URL,
-    description=SHORT_DESCRIPTION,
-    long_description=read('README.md'),
-    long_description_content_type='text/markdown',
-    packages=setuptools.find_packages(exclude='tests'),
-    python_requires='>=3.7',
-    install_requires=REQUIRES,
-    classifiers=[
-        'Framework :: Pytest',
-        'Intended Audience :: Developers',
-        'Topic :: Software Development :: Testing',
-        'Development Status :: 5 - Production/Stable',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Programming Language :: Python :: 3.10',
-        'Programming Language :: Python :: 3.11',
-        'Programming Language :: Python :: 3 :: Only',
-        'Operating System :: OS Independent',
-        'License :: OSI Approved :: MIT License',
-    ],
-    entry_points=ENTRY_POINTS,
-)
diff --git a/pytest-embedded-serial/LICENSE b/pytest-embedded-serial/LICENSE
new file mode 100644
index 00000000..d73ab57c
--- /dev/null
+++ b/pytest-embedded-serial/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/pytest-embedded-serial/pyproject.toml b/pytest-embedded-serial/pyproject.toml
new file mode 100644
index 00000000..6a72fd7a
--- /dev/null
+++ b/pytest-embedded-serial/pyproject.toml
@@ -0,0 +1,40 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "pytest-embedded-serial"
+authors = [
+    {name = "Fu Hanxi", email = "fuhanxi@espressif.com"},
+]
+readme = "README.md"
+license = {file = "LICENSE"}
+classifiers = [
+    "Development Status :: 5 - Production/Stable",
+    "Framework :: Pytest",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: MIT License",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python",
+    "Topic :: Software Development :: Testing",
+]
+dynamic = ["version", "description"]
+requires-python = ">=3.7"
+
+dependencies = [
+    "pytest-embedded~=1.3.1",
+    "pyserial~=3.0",
+]
+
+[project.urls]
+homepage = "https://github.com/espressif/pytest-embedded"
+repository = "https://github.com/espressif/pytest-embedded"
+documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
+changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
diff --git a/pytest-embedded-serial/pytest_embedded_serial/__init__.py b/pytest-embedded-serial/pytest_embedded_serial/__init__.py
index 8d4d4514..478963d9 100644
--- a/pytest-embedded-serial/pytest_embedded_serial/__init__.py
+++ b/pytest-embedded-serial/pytest_embedded_serial/__init__.py
@@ -1,3 +1,5 @@
+"""Make pytest-embedded plugin work with Serial."""
+
 from .dut import SerialDut
 from .serial import Serial
 
@@ -5,3 +7,5 @@
     'Serial',
     'SerialDut',
 ]
+
+__version__ = '1.3.1'
diff --git a/pytest-embedded-serial/setup.py b/pytest-embedded-serial/setup.py
deleted file mode 100644
index bed9bbc2..00000000
--- a/pytest-embedded-serial/setup.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import codecs
-import os
-import re
-
-import setuptools
-from setuptools import setup
-
-
-def read(fname):
-    file_path = os.path.join(os.path.dirname(__file__), fname)
-    return codecs.open(file_path, encoding='utf-8').read()
-
-
-def get_version():
-    regex = re.compile(r'version = "(.+)"')
-    return regex.findall(read(os.path.join('..', 'pyproject.toml')))[0]
-
-
-AUTHOR = 'Fu Hanxi'
-EMAIL = 'fuhanxi@espressif.com'
-NAME = 'pytest-embedded-serial'
-SHORT_DESCRIPTION = 'pytest embedded plugin for testing serial ports'
-LICENSE = 'MIT'
-URL = 'https://docs.espressif.com/projects/pytest-embedded/en/latest/'
-VERSION = get_version()
-REQUIRES = [
-    f'pytest-embedded~={VERSION}',
-    'pyserial~=3.0',
-]
-ENTRY_POINTS = {}
-
-setup(
-    name=NAME,
-    version=VERSION,
-    author=AUTHOR,
-    author_email=EMAIL,
-    license=LICENSE,
-    url=URL,
-    description=SHORT_DESCRIPTION,
-    long_description=read('README.md'),
-    long_description_content_type='text/markdown',
-    packages=setuptools.find_packages(exclude='tests'),
-    python_requires='>=3.7',
-    install_requires=REQUIRES,
-    classifiers=[
-        'Framework :: Pytest',
-        'Intended Audience :: Developers',
-        'Topic :: Software Development :: Testing',
-        'Development Status :: 5 - Production/Stable',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Programming Language :: Python :: 3.10',
-        'Programming Language :: Python :: 3.11',
-        'Programming Language :: Python :: 3 :: Only',
-        'Operating System :: OS Independent',
-        'License :: OSI Approved :: MIT License',
-    ],
-    entry_points=ENTRY_POINTS,
-)
diff --git a/pytest-embedded/LICENSE b/pytest-embedded/LICENSE
new file mode 100644
index 00000000..d73ab57c
--- /dev/null
+++ b/pytest-embedded/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/pytest-embedded/pyproject.toml b/pytest-embedded/pyproject.toml
new file mode 100644
index 00000000..074a815c
--- /dev/null
+++ b/pytest-embedded/pyproject.toml
@@ -0,0 +1,41 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "pytest-embedded"
+authors = [{name = "Fu Hanxi", email = "fuhanxi@espressif.com"}]
+readme = "README.md"
+license = {file = "LICENSE"}
+classifiers = [
+    "Development Status :: 5 - Production/Stable",
+    "Framework :: Pytest",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: MIT License",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python",
+    "Topic :: Software Development :: Testing",
+]
+dynamic = ["version", "description"]
+requires-python = ">=3.7"
+
+dependencies = [
+    "pytest>=7.0",
+    "pexpect>=4.4",
+]
+
+[project.urls]
+homepage = "https://github.com/espressif/pytest-embedded"
+repository = "https://github.com/espressif/pytest-embedded"
+documentation = "https://docs.espressif.com/projects/pytest-embedded/en/latest/"
+changelog = "https://github.com/espressif/pytest-embedded/blob/main/CHANGELOG.md"
+
+[project.entry-points."pytest11"]
+pytest_embedded = "pytest_embedded.plugin"
diff --git a/pytest-embedded/pytest_embedded/__init__.py b/pytest-embedded/pytest_embedded/__init__.py
index e92e0b17..a3023af7 100644
--- a/pytest-embedded/pytest_embedded/__init__.py
+++ b/pytest-embedded/pytest_embedded/__init__.py
@@ -1,4 +1,8 @@
+"""A pytest plugin that designed for embedded testing."""
+
 from .app import App
 from .dut import Dut
 
 __all__ = ['App', 'Dut']
+
+__version__ = '1.3.1'
diff --git a/pytest-embedded/setup.py b/pytest-embedded/setup.py
deleted file mode 100644
index d4a54afe..00000000
--- a/pytest-embedded/setup.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import codecs
-import os
-import re
-
-import setuptools
-from setuptools import setup
-
-
-def read(fname):
-    file_path = os.path.join(os.path.dirname(__file__), fname)
-    return codecs.open(file_path, encoding='utf-8').read()
-
-
-def get_version():
-    regex = re.compile(r'version = "(.+)"')
-    return regex.findall(read(os.path.join('..', 'pyproject.toml')))[0]
-
-
-AUTHOR = 'Fu Hanxi'
-EMAIL = 'fuhanxi@espressif.com'
-NAME = 'pytest-embedded'
-SHORT_DESCRIPTION = 'pytest embedded plugin'
-LICENSE = 'MIT'
-URL = 'https://docs.espressif.com/projects/pytest-embedded/en/latest/'
-REQUIRES = [
-    'pytest>=7.0',
-    'pexpect>=4.4',
-]
-ENTRY_POINTS = {
-    'pytest11': [
-        'pytest_embedded = pytest_embedded.plugin',
-    ],
-}
-
-setup(
-    name=NAME,
-    version=get_version(),
-    author=AUTHOR,
-    author_email=EMAIL,
-    license=LICENSE,
-    url=URL,
-    description=SHORT_DESCRIPTION,
-    long_description=read('README.md'),
-    long_description_content_type='text/markdown',
-    packages=setuptools.find_packages(exclude='tests'),
-    python_requires='>=3.7',
-    install_requires=REQUIRES,
-    classifiers=[
-        'Framework :: Pytest',
-        'Intended Audience :: Developers',
-        'Topic :: Software Development :: Testing',
-        'Development Status :: 5 - Production/Stable',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Programming Language :: Python :: 3.10',
-        'Programming Language :: Python :: 3.11',
-        'Programming Language :: Python :: 3 :: Only',
-        'Operating System :: OS Independent',
-        'License :: OSI Approved :: MIT License',
-    ],
-    entry_points=ENTRY_POINTS,
-)
diff --git a/pytest-embedded/tests/test_base.py b/pytest-embedded/tests/test_base.py
index 04d8310f..34ea16fb 100644
--- a/pytest-embedded/tests/test_base.py
+++ b/pytest-embedded/tests/test_base.py
@@ -581,8 +581,12 @@ def test_temp_disable_packages():
 def test_quick_example(testdir):
     testdir.makepyfile(r"""
     from pytest_embedded import Dut
+    import pytest
 
     def test_quick_example(redirect, dut: Dut):
+        with pytest.raises(ImportError):
+            import pytest_embedded_serial
+
         with redirect():
             print('this would be redirected')