diff --git a/newsfragments/+build-with-uv.feature b/newsfragments/+build-with-uv.feature
new file mode 100644
index 0000000000000000000000000000000000000000..3913ea630f62cf1bd994e0a319ded55354676c1a_bmV3c2ZyYWdtZW50cy8rYnVpbGQtd2l0aC11di5mZWF0dXJl
--- /dev/null
+++ b/newsfragments/+build-with-uv.feature
@@ -0,0 +1,1 @@
+Build python packages with uv when available
\ No newline at end of file
diff --git a/odoo_scripts/docker_build_clean.py b/odoo_scripts/docker_build_clean.py
index 75ef4de48aecb18ffbe93fb06b657ee743a4948a_b2Rvb19zY3JpcHRzL2RvY2tlcl9idWlsZF9jbGVhbi5weQ==..3913ea630f62cf1bd994e0a319ded55354676c1a_b2Rvb19zY3JpcHRzL2RvY2tlcl9idWlsZF9jbGVhbi5weQ== 100644
--- a/odoo_scripts/docker_build_clean.py
+++ b/odoo_scripts/docker_build_clean.py
@@ -8,7 +8,7 @@
 from typing import List, Optional
 
 from ._parsing import apply, basic_parser
-from .docker_build_copy import _PYTHON_PACKAGES_DIR
+from .docker_build_copy import _ODOO_MODULES_DIR, _PYTHON_PACKAGES_DIR
 from .list_modules import MODULES_LIST_FILE
 
 _logger = logging.getLogger(__name__)
@@ -51,7 +51,7 @@
     """Clean up after a build"""
     if os.path.exists(MODULES_LIST_FILE):
         os.remove(MODULES_LIST_FILE)
-    for directory in ("odoo_modules", "odoo_setup", _PYTHON_PACKAGES_DIR):
+    for directory in (_ODOO_MODULES_DIR, "odoo_setup", _PYTHON_PACKAGES_DIR):
         if os.path.exists(directory):
             shutil.rmtree(directory)
 
diff --git a/odoo_scripts/docker_build_copy.py b/odoo_scripts/docker_build_copy.py
index 75ef4de48aecb18ffbe93fb06b657ee743a4948a_b2Rvb19zY3JpcHRzL2RvY2tlcl9idWlsZF9jb3B5LnB5..3913ea630f62cf1bd994e0a319ded55354676c1a_b2Rvb19zY3JpcHRzL2RvY2tlcl9idWlsZF9jb3B5LnB5 100644
--- a/odoo_scripts/docker_build_copy.py
+++ b/odoo_scripts/docker_build_copy.py
@@ -25,5 +25,5 @@
 
 _logger = logging.getLogger(__name__)
 
-__version__ = "2.6.3"
+__version__ = "2.7.0"
 __date__ = "2020-06-30"
@@ -29,3 +29,3 @@
 __date__ = "2020-06-30"
-__updated__ = "2024-12-03"
+__updated__ = "2024-12-11"
 
@@ -31,6 +31,6 @@
 
-_ODOO_MODULES_DIR = "odoo_modules"
-_PYTHON_PACKAGES_DIR = "python_packages"
+_ODOO_MODULES_DIR: str = "odoo_modules"
+_PYTHON_PACKAGES_DIR: str = "python_packages"
 
 
 def add_build_copy_options(parser: argparse.ArgumentParser):
@@ -380,5 +380,5 @@
 
 def _compile_package(package_to_compile, target) -> List[str]:
     versions = _find_packages_version(package_to_compile, tags_only=False)
-    content = []
+    content: list[str] = []
     requirements = []
@@ -384,5 +384,7 @@
     requirements = []
+    cachedirname: str = ""
+    tmpdirname: str = ""
     if versions:
         cachedirname = compiled_cache_dir(versions[0][0], versions[0][1])
         if not os.path.exists(cachedirname):
             os.makedirs(cachedirname)
@@ -385,9 +387,13 @@
     if versions:
         cachedirname = compiled_cache_dir(versions[0][0], versions[0][1])
         if not os.path.exists(cachedirname):
             os.makedirs(cachedirname)
-        content = os.listdir(cachedirname)
-    if not content:
-        package_path = os.path.join(".", package_to_compile)
-        with tempfile.TemporaryDirectory() as tmpdirname:
+        _logger.info("Using cache for %s", package_to_compile)
+        content = [os.path.join(cachedirname, f) for f in os.listdir(cachedirname)]
+    try:
+        if not content:
+            _logger.info("Compiling %s", package_to_compile)
+            package_path = os.path.join(".", package_to_compile)
+            tmpdirname = tempfile.mkdtemp()
+            # Faster is using uv
             try:
@@ -393,7 +399,2 @@
             try:
-                # TODO pip wheel semble plus rapide
-                # build is not usable directly so use a subprocess.
-                # Disable flake8, this is just to test that the package is installed.
-                import build  # type: ignore[import] # noqa: F401
-
                 cmd = [
@@ -399,4 +400,3 @@
                 cmd = [
-                    sys.executable,
-                    "-m",
+                    "uv",
                     "build",
@@ -402,5 +402,6 @@
                     "build",
-                    "--outdir",
+                    "--wheel",
+                    "--out-dir",
                     tmpdirname,
                     package_path,
                 ]
@@ -404,5 +405,7 @@
                     tmpdirname,
                     package_path,
                 ]
+                if not _logger.getChild("compile_package").isEnabledFor(logging.DEBUG):
+                    cmd.append("--quiet")
                 _logger.debug(" ".join(cmd))
                 check_call(cmd)
@@ -407,24 +410,45 @@
                 _logger.debug(" ".join(cmd))
                 check_call(cmd)
-            except ImportError:
-                # pip is not usable directly, it is indicated in its doc to use
-                # subprocess instead.
-                cmd = [
-                    sys.executable,
-                    "-m",
-                    "pip",
-                    "wheel",
-                    "--no-deps",
-                    "-w",
-                    tmpdirname,
-                    "--ignore-requires-python",
-                    package_path,
-                ]
-            _logger.debug(" ".join(cmd))
-            check_call(cmd)
-            content = os.listdir(tmpdirname)
-            for file in content:
-                # LINUX
-                check_call(["cp", os.path.join(tmpdirname, file), target])
-                if versions:
+            except FileNotFoundError:
+                try:
+                    # TODO pip wheel semble plus rapide
+                    # build is not usable directly so use a subprocess.
+                    # Disable flake8, this is just to test that the package is
+                    # installed.
+                    import build  # type: ignore[import] # noqa: F401
+
+                    cmd = [
+                        sys.executable,
+                        "-m",
+                        "build",
+                        "--outdir",
+                        tmpdirname,
+                        package_path,
+                    ]
+                    _logger.debug(" ".join(cmd))
+                    check_call(cmd)
+                except ImportError:
+                    # pip is not usable directly, it is indicated in its doc to use
+                    # subprocess instead.
+                    cmd = [
+                        sys.executable,
+                        "-m",
+                        "pip",
+                        "wheel",
+                        "--no-deps",
+                        "-w",
+                        tmpdirname,
+                        "--ignore-requires-python",
+                        package_path,
+                    ]
+                _logger.debug(" ".join(cmd))
+                check_call(cmd)
+            # could also filter to use only .whl
+            content = [
+                os.path.join(tmpdirname, f)
+                for f in os.listdir(tmpdirname)
+                if f not in (".gitignore", ".hgignore")
+            ]
+            if cachedirname:
+                for file in content:
                     # LINUX
@@ -430,5 +454,4 @@
                     # LINUX
-                    check_call(["cp", os.path.join(target, file), cachedirname])
-    else:
+                    check_call(["cp", "-a", file, cachedirname])
         for file in content:
             # LINUX
@@ -433,9 +456,12 @@
         for file in content:
             # LINUX
-            check_call(["cp", os.path.join(cachedirname, file), target])
-    for file in content:
-        requirements.append(os.path.join(".", target, file))
-
+            check_call(["cp", "-a", file, target])
+            requirements.append(os.path.join(".", target, os.path.basename(file)))
+    finally:
+        if tmpdirname:
+            for file in os.listdir(tmpdirname):
+                os.remove(os.path.join(tmpdirname, file))
+            os.rmdir(tmpdirname)
     try:
         # Disable flake8, this is just to test that the package is installed
         import twine  # type: ignore[import] # noqa: F401