auto generate requirements file when missing

This commit is contained in:
David Asamu
2025-10-10 00:08:25 +01:00
parent b456b82ee4
commit 81efce7fe7
+31 -121
View File
@@ -851,47 +851,6 @@ class LocalDeps(NamedTuple):
additional_contexts: list[pathlib.Path] = None
def _get_package_metadata_files(package_path: pathlib.Path) -> list[str]:
"""Return list of metadata files that exist in package_path.
These files are needed for dependency resolution and should be copied
before source code to optimize Docker layer caching.
"""
metadata_files = [
# Core build files
"pyproject.toml",
"setup.py",
"setup.cfg",
"requirements.txt",
# Referenced in pyproject.toml
"README.md",
"README.rst",
"README.txt",
"LICENSE",
"LICENSE.txt",
"MANIFEST.in",
# Lock files for reproducible builds
"uv.lock",
"poetry.lock",
"Pipfile.lock",
"pdm.lock",
# Version files
"VERSION",
"_version.py",
"__version__.py",
]
existing_files = []
for file in metadata_files:
if (package_path / file).exists():
existing_files.append(file)
return existing_files
def _assemble_local_deps(config_path: pathlib.Path, config: Config) -> LocalDeps:
config_path = config_path.resolve()
# ensure reserved package names are not used
@@ -964,6 +923,7 @@ def _assemble_local_deps(config_path: pathlib.Path, config: Config) -> LocalDeps
# set working_dir
if local_dep == ".":
working_dir = f"/deps/{container_name}"
requirement_path = f"/deps/{container_name}/requirements.txt"
else:
# We could not find a pyproject.toml or setup.py, so treat as a faux package
if any(file == "__init__.py" for file in files):
@@ -995,17 +955,18 @@ def _assemble_local_deps(config_path: pathlib.Path, config: Config) -> LocalDeps
faux_pkgs[resolved] = (local_dep, container_path)
if local_dep == ".":
working_dir = container_path
requirement_path = f"{container_path}/requirements.txt"
# If the faux package has a requirements.txt, we'll add
# the path to the list of requirements to install.
if "requirements.txt" in files:
rfile = resolved / "requirements.txt"
pip_reqs.append(
(
rfile,
f"{container_path}/requirements.txt",
)
# If the package has a requirements.txt, we'll add
# the path to the list of requirements to install.
if "requirements.txt" in files:
rfile = resolved / "requirements.txt"
pip_reqs.append(
(
rfile,
requirement_path,
)
)
return LocalDeps(pip_reqs, real_pkgs, faux_pkgs, working_dir, additional_contexts)
@@ -1395,73 +1356,18 @@ RUN set -ex && \\
for fullpath, (relpath, destpath) in local_deps.faux_pkgs.items()
)
# Optimized two-stage copy for better Docker layer caching:
# Stage 1: Copy only metadata files (rarely change)
# Stage 2: Install dependencies (cached until metadata changes)
# Stage 3: Copy full source code (changes frequently)
# Stage 4: Quick reinstall without dependency resolution (fast)
local_pkgs_metadata_str_parts = []
local_pkgs_install_str_parts = []
local_pkgs_source_str_parts = []
local_pkgs_reinstall_str_parts = []
for fullpath, (relpath, name) in local_deps.real_pkgs.items():
metadata_files = _get_package_metadata_files(fullpath)
if fullpath in local_deps.additional_contexts:
# For additional contexts, use COPY --from
if metadata_files:
copy_commands = "\n".join(
f"COPY --from={name} {f} /deps/{name}/{f}"
for f in metadata_files
)
local_pkgs_metadata_str_parts.append(
f"""# -- Adding package metadata for {name} --
RUN mkdir -p /deps/{name}
{copy_commands}
# -- End of package metadata for {name} --"""
)
local_pkgs_source_str_parts.append(
f"""# -- Adding full source for {relpath} --
local_pkgs_str = os.linesep.join(
(
f"""# -- Adding local package {relpath} --
COPY --from={name} . /deps/{name}
# -- End of full source for {relpath} --"""
)
else:
# For local paths, use ADD
if metadata_files:
copy_commands = "\n".join(
f"ADD {relpath}/{f} /deps/{name}/{f}"
for f in metadata_files
)
local_pkgs_metadata_str_parts.append(
f"""# -- Adding package metadata for {name} --
RUN mkdir -p /deps/{name}
{copy_commands}
# -- End of package metadata for {name} --"""
)
local_pkgs_source_str_parts.append(
f"""# -- Adding full source for {relpath} --
# -- End of local package {relpath} --"""
if fullpath in local_deps.additional_contexts
else f"""# -- Adding local package {relpath} --
ADD {relpath} /deps/{name}
# -- End of full source for {relpath} --"""
)
# Install with dependency resolution (cached if metadata unchanged)
local_pkgs_install_str_parts.append(
f"RUN cd /deps/{name} && {global_reqs_pip_install} ."
# -- End of local package {relpath} --"""
)
# Quick reinstall without dependency resolution (fast, after source changes)
local_pkgs_reinstall_str_parts.append(
f"RUN cd /deps/{name} && {global_reqs_pip_install} --no-deps ."
)
local_pkgs_metadata_str = os.linesep.join(local_pkgs_metadata_str_parts)
local_pkgs_install_str = os.linesep.join(local_pkgs_install_str_parts)
local_pkgs_source_str = os.linesep.join(local_pkgs_source_str_parts)
local_pkgs_reinstall_str = os.linesep.join(local_pkgs_reinstall_str_parts)
for fullpath, (relpath, name) in local_deps.real_pkgs.items()
)
install_node_str: str = (
"RUN /storage/install-node.sh"
@@ -1477,11 +1383,8 @@ ADD {relpath} /deps/{name}
pip_config_file_str,
pip_pkgs_str,
pip_reqs_str,
local_pkgs_metadata_str, # Metadata first for caching
local_pkgs_str,
faux_pkgs_str,
local_pkgs_install_str, # Install with deps (cached layer)
local_pkgs_source_str, # Source code after dependencies
local_pkgs_reinstall_str, # Quick reinstall without deps
],
)
)
@@ -1523,14 +1426,21 @@ ADD {relpath} /deps/{name}
image_str = docker_tag(config, base_image, api_version)
docker_file_contents = [
f"FROM {image_str}",
"# -- making use of two-stage-cache-optimizations --",
"",
"# -- making use of requirements.txt optimizations --",
os.linesep.join(config["dockerfile_lines"]),
"",
installs,
"",
# Note: Individual package installs are now in 'installs' above
# This optimizes caching by separating metadata from source code
"# -- Installing all local dependencies --",
f"""RUN for dep in /deps/*; do \
echo "Installing $dep"; \
if [ -d "$dep" ]; then \
echo "Installing $dep"; \
(cd "$dep" && {global_reqs_pip_install} .); \
fi; \
done""",
"# -- End of local dependencies install --",
os.linesep.join(env_vars),
"",
js_inst_str,