diff --git a/src/pythainer/builders/__init__.py b/src/pythainer/builders/__init__.py index 04993c2..e0251bd 100644 --- a/src/pythainer/builders/__init__.py +++ b/src/pythainer/builders/__init__.py @@ -7,12 +7,14 @@ tailored specifically for Docker environments. """ import os +import shutil import tempfile from pathlib import Path from typing import Dict, List from pythainer.builders.cmds import ( AddPkgDockerBuildCommand, + CopyDockerBuildCommand, DockerBuildCommand, StrDockerBuildCommand, ) @@ -220,16 +222,15 @@ def add_packages(self, packages: List[str]) -> None: """ self._build_commands.append(AddPkgDockerBuildCommand(packages=packages)) - def copy(self, filename: PathType, destination: PathType) -> None: + def copy(self, source_path: Path, destination_path: Path) -> None: """ Copies a file to the docker container Parameters: - filename (PathType): The file to copy to the container. - destination (PathType): The location to place the file within the Docker container. + source_path (Path): The file or folder to copy to the container. + destination_path (Path): The location to place the file or folder within the Docker container. """ - cmd = f"COPY {filename} {destination}" - self._build_commands.append(StrDockerBuildCommand(cmd)) + self._build_commands.append(CopyDockerBuildCommand(source_path,destination_path)) class DockerBuilder(PartialDockerBuilder): @@ -389,12 +390,14 @@ def build(self, dockerfile_savepath: PathType = "", docker_context: PathType = " Parameters: dockerfile_savepath (PathType): Optional path to save the Dockerfile used for the build. """ + main_dir = Path("/tmp/pythainer/docker/") mkdir(main_dir) with tempfile.TemporaryDirectory( prefix="/tmp/pythainer/docker/docker-build-", dir=main_dir, ) as temp_dir: + temp_path = Path(temp_dir) dockerfile_path = (temp_path / "Dockerfile").resolve() dockerfile_paths = [dockerfile_path] + ( @@ -402,6 +405,10 @@ def build(self, dockerfile_savepath: PathType = "", docker_context: PathType = " ) self.generate_dockerfile(dockerfile_paths=dockerfile_paths) + data_path = main_dir / "data" + + shutil.move(data_path, temp_path) + command = self.get_build_commands( dockerfile_path=dockerfile_path, docker_build_dir=Path(docker_context).resolve() if docker_context else temp_path, @@ -418,6 +425,7 @@ def build(self, dockerfile_savepath: PathType = "", docker_context: PathType = " output_is_log=True, ) + def get_runner( self, workdir: PathType | None = None, diff --git a/src/pythainer/builders/cmds.py b/src/pythainer/builders/cmds.py index 2aadb37..ce2ecd1 100644 --- a/src/pythainer/builders/cmds.py +++ b/src/pythainer/builders/cmds.py @@ -9,6 +9,9 @@ and package managers. """ +import os +from pathlib import Path +import shutil from typing import List @@ -68,6 +71,52 @@ def get_str_for_dockerfile( """ return str(self._str) +class CopyDockerBuildCommand(DockerBuildCommand): + """ + Represents a simple string command in a Dockerfile, such as a comment or other directive that + does not involve complex logic or conditional behavior. + """ + + def __init__(self, source_path:Path ,destination_path:Path) -> None: + """ + Initializes the StrDockerBuildCommand with a string. + + Parameters: + s (str): The string that represents this Dockerfile command. + """ + super().__init__() + self._source_path = source_path + self._destination_path = destination_path + + # pylint: disable=arguments-differ + def get_str_for_dockerfile( + self, + docker_file_Path: Path, + *args, + **kwargs, + ) -> str: + """ + Returns the string that was initialized at the creation of the object. + + Returns: + str: The command string. + """ + + data_path = Path("/tmp/pythainer/docker/data") + + if os.path.isfile(self._source_path): + shutil.copyfile(self._source_path, data_path / self._source_path) + elif os.path.isdir(self._source_path): + shutil.copytree(self._source_path, data_path / self._source_path,dirs_exist_ok=True) + else: + raise FileExistsError(f'{self._source_path} is not a valid target to copy into the docker container') + + + + cmd = f"COPY --chown=${{USER_NAME}} {self._source_path} {self._destination_path}" + + return cmd + class AddPkgDockerBuildCommand(DockerBuildCommand): """ diff --git a/src/pythainer/examples/builders/__init__.py b/src/pythainer/examples/builders/__init__.py index 82f96fb..c275cac 100644 --- a/src/pythainer/examples/builders/__init__.py +++ b/src/pythainer/examples/builders/__init__.py @@ -7,7 +7,9 @@ projects like CLSPV. """ -from typing import Iterable, List +import os +from pathlib import Path +from typing import Iterable, List, Optional from pythainer.builders import PartialDockerBuilder, UbuntuDockerBuilder from pythainer.builders.utils import cmake_build_install @@ -419,3 +421,56 @@ def qemu_builder( builder.workdir(path="..") return builder + +def pyvenv_builder( + requirements_file_path:Optional[Path] = None, + dependency_folders:Optional[List[Path]] = None, + single_packages: Optional[List[str]] = None, + lib_dir:Path = Path("/home/${USER_NAME}/workspace/libraries") +) -> PartialDockerBuilder: + + """ + Create a python virtual environment given the specified parameters + + Parameters: + requirements_file_path (Optional[Path] = None): + Path to a file structured like a requirements.txt + dependency_folders (Optional[List[Path]] = None): + A list of paths that point towards folders with their own + setup.py or pyproject.toml file, to be added to the venv + single_packages: + A list of names of packages such as + "numpy" or "matplotlib" to be added to the venv. + lib_dir: + lib_dir (Path): Directory for libraries and tools. + + Returns: + PartialDockerBuilder: A builder that, when composed/applied, creates a venv in the container. + """ + + venv_dir = lib_dir / "./python_venv/" + builder = PartialDockerBuilder() + + builder.desc("install the python cvenv") + builder.user() + builder.run("mkdir {lib_dir}") + builder.workdir(venv_dir) + + builder.run("python3 -m venv .cvenv") + builder.run("./.cvenv/bin/python3 -m pip install --upgrade setuptools pip") + + if single_packages: + packagelist = " ".join(single_packages) + builder.run(f"./.cvenv/bin/python3 -m pip install --upgrade {packagelist}") + + if requirements_file_path: + builder.copy(requirements_file_path, venv_dir / "./requirements_file/requirements.txt") + builder.run(f"./.cvenv/bin/python3 -m pip install -r {venv_dir / "./requirements_file/requirements.txt"}") + + if dependency_folders: + for path in dependency_folders: + builder.copy(path,venv_dir / f"./dependencies/{os.path.basename(os.path.normpath(path))}") + builder.run(f"./.cvenv/bin/python3 -m pip install -e ./dependencies/{os.path.basename(os.path.normpath(path))}/") + + builder.env(name="PATH", value=f"$PATH:{venv_dir / ".cvenv/bin/"}") + return builder