Creates conda development shells and provides related convenience tooling.
The following subsection provides instructions on obtaining, installing, and activating conda. The two remaining subsections describe two methods for installing condev and its required tools into your conda installation: 1. Using a prebuilt package, or 2. Bootstrapping.
conda itself may be provided by a Miniconda, Miniforge, Mambaforge, or Anaconda installation. Prefer one of the first three for a lightweight installation providing only what you need. Miniconda is the official distribution from Anaconda, Inc., and defaults to using their package collection. Miniforge is equivalent to Miniconda except that it defaults to using the conda-forge package collection: A community-curated collection of high-quality packages for which all recipes are available for public inspection. Mambaforge is identical to Miniforge except that includes the mamba tools. If you are unsure, use Miniforge, as shown below.
wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh
bash Miniforge3-Linux-x86_64.sh -bfp /desired/path/to/conda
source /desired/path/to/conda/etc/profile.d/conda.sh
conda activateWith your conda activated, the following command install the latest available condev package:
conda install -y -c maddenp condevThis also installs dependency packages providing:
conda-buildandconda-verifyfor building conda packages and interpreting their metadatajqfor extracting select metadata values from conda-package metadata- A late-model
makefor using the convenient targets defined by thecondevMakefiles
You can also search the maddenp channel for available versions with conda search -c maddenp --override-channels condev and install a specific version by replacing condev with condev=<version>[=build] in the preceding conda install command.
With your conda activated, the following steps install required dependency packages, builds the condev conda package, installs that package into the base environment, then verifies that the expected condev programs are available:
conda install -y conda-build conda-verify jq make
make package
conda install -y -c local condev=$(jq -r .version recipe/meta.json)=$(jq -r .build recipe/meta.json)
which condev-meta condev-shellAfter executing this procedure, you should have a condev-equipped conda installation that you can activate in the future with the command source /path/to/condev/conda/etc/profile.d/conda.sh && conda activate.
If you adhere to a few conventions in the layout and configuration of your project, you can use your condev-equipped conda installation to:
- Obtain a development shell in which you can edit and test your code interactively, in an isolated environment with all dependency libraries available, with
make devshell - Build a conda package based on your project's recipe with the
make packagecommand - Create a conda environment based on your project's recipe with the
make envcommand - Execute your project's defined code-quality tests with the
make testcommand (or the more granularmake lint,make typecheck, andmake unittestcommands) - Auto-format your Python code with the
make formatcommand
The Bash shell created by make devshell may start with only a subset of the host shell's environment. If a .condevrc file exists in your home directory, it will be sourced to do any environment setup you require. For example, you might create a ~/.condevrc file with the content source ~/.bashrc.
The demo subdirectory of this repo offers a prototype project (i.e. files/directories that would appear in the root of a separate git repository) that leverages condev to demonstrate the use of the various convenience make targets. It also demonstrates one way in which hybrid Python/C projects might be handled.
Use make devshell to create and activate a conda environment in which all dependency packages are available, and package's Python code is live-linked for a fast edit/test loop. After executing the the Getting Started procedure above, try the following:
cd demo
make devshellThis will create a conda environment with your project's build, host, run, and test packages all installed and ready to use. The Python code under src/hello is live-linked into the environment via a setuptools editable install, so any changes you make can be immediately executed, tested, etc. You will see your Bash prompt prefixed with the name of the development environment, (DEV-hello).
If this were a pure-Python codebase, you could now run the heythere entry-point console script (defined by src/setup.py). Try that and see how it fails. Since this project's Python code relies on a C function (which also lives in the project, so cannot be satisfied by installing a dependency package), that needs to be build first:
make machineNow run heythere and it should succeed.
The demo project provides auto-formatting via black and isort. Run make format to auto-format all Python code in the project. This should do nothing if you haven't changed the code since you cloned the repo, but feel free to edit e.g. src/hello/core.py, then run make format to see the tools in action.
You can run code-quality tools -- linter, type checker, and unit tests with a coverage report -- by running make test. These tests rely on pylint, mypy, pytest, and coverage, primarily configured via the pyproject.toml file in the project's src/ directory. You can also run tools individually with make lint, make typecheck, and make unittest. Again, these tools should all pass if you have not changed the code (provided you have build the C code as described above), but you can tweak the code to see how the tools respond to code-quality issues.
When you are finished with development work, you can simply exit the development shell with exit or CTRL-D. If you later type make devshell again, the existing environment will be activated nearly instantaneously.
NB: As a rule of thumb, you should manually remove your development environment (e.g. conda env remove -n DEV-demo) if you change the contents of recipe/, and especially if you change the meta.yaml file, which defines required dependency packages. Likewise, the file recipe/meta.json is generated by several make targets, but only if certain other files under recipe/ change. There could be other circumstances under which this file should be regenerated, however, so manually remove it and re-run your make command if in doubt.
To support automation scenarios, you may export environment variable CONDEV_SHELL_CMD and then run make devshell (or its underlying condev-shell script directly), which will result in the variable's value being executed as a command in the devlopment shell, followed by the development shell exiting and returning control to the caller.
While not in a development shell (exit from DEV-hello if necessary), run make package to invoke conda-build to build the demo code as a conda package. (Note that the same tests run by make test will be run by conda-build, and must succeed for the final package to be built -- so to save time, ensure that make test in a development shell succeeds before attempting make package.) When the package has been built, you can verify its availability with
conda search -c local --override-channels helloDepending on your needs, you could upload this package to a public Anaconda channel, to a private conda-package server, or simply access it from local disk, creating conda environments that include it.
Use make env to create a conda package based on the current directory's recipe, then create a conda environment based on that package -- pulling in any run-time dependency packages it declares in its recipe/meta.yaml file -- named based on the package name, version number, and build number. Try:
make env
version=$(jq -r .version recipe/meta.json)
buildnum=$(jq -r .buildnum recipe/meta.json)
conda activate hello-$version-$buildnum
heythereThe demo project demonstrates a number of conventions (or in some case requirements) you may find helpful when working with condev and/or with conda and/or with Python projects in general:
recipe/build.shis a standardconda-buildfile and is executed to build your code and install it into theconda-build-supplied$PREFIXtree for packaging. Simple build recipes can be provided directly inmeta.yamlas an alternative, butbuild.shallows maximum flexibility.recipe/channelsspecifies a list of channels conda is allowed to use to obtain packages. In calls toconda create,conda install, etc., these channels are translated into-c(aka--channel) command-line options. Channels are consulted in priority order; packages satisfying requirements are taken from the highest-priority channel. Thelocalchannel (i.e. packages built by and contained in your local conda installation) is always implicitly added as the lowest-priority channel. This file is required by thecondevtools, but is not a standard file from the perspective ofconda-build.recipe/meta.yamlis well described.recipe/run_test.shis a standardconda-buildfile, is executed late in the package-build process, and must run successfully for the build to complete. Normally, the tests executed in this script are meant to test the built and packaged code in its run-time environment; however, thedemoproject, by adding appropriate entries to thetestsource_filessection ofmeta.yaml, also arranges for everything available in a development shell to also be available atconda-buildtest time, such that the same code-quality tests executed bymake testat development time can be exercised one last time before the final package is built. (In fact,make testworks by executingrun_test.shin the development shell, to avoid build-time surprises.) It would be appropriate to extend these tests to perform integration tests with realistic data (perhaps by creating test-data conda packages and having them installed via thetestrequiressection inmeta.yaml) that are not part of the development-environment code-quality tests, but this is not done in thedemoproject.src/pyproject.tomlspecifies most options for the code-quality tools listed in thetestrequiressection ofmeta.yamland exercised bymake test/recipe/run_test.sh. Modify it to your liking.src/setup.pyis used both to install Python code (and potentially non-Python scripts, as shown in thedemoproject) at package build time, and to create an "editable install" into the development shell. It relies on metadata extracted by the project's conda recipe by thecondev-metatool, which is then written torecipe/meta.json. The projectMakefileensures that, except for in some rare cases,meta.jsonis regenerated as needed. So, prefer to rely on the providedmaketargets when possible: Thecondev-shellprogram expects to be run in a directory wheresrc/setup.pyis available, and runningmake devshell(ormake -C /path/to/repo/root devshell) ensures that, ifsrc/setup.pyexists, it will be found. If you need to callcondev-shelldirectly, do so from the repo root if you have asrc/setup.py.src/setup.pyalso defines so-called entry-point console scripts: The command-line programs that your package provides users. Prefer writing your Python code as a library (i.e. do not include#!at the top of, orif __name__ == "__main__"at the bottom of, your.pyfiles) and rely onsetuptools(viasetup.py) to create and install entry-point scripts that load and call the appropriate package, module, and function.- Use
importlib.resourcesto provide configuration files, etc., to your code. Python will always know where to find resources so configured, regardless of whether your code is running in a development shell or a final, deployed conda environment, eliminating any headaches about paths. - If these conventions are followed,
demo/Makefileshould be usable for most any Python project.
- There is a lot about
conda-buildthat all this glosses over. It is a powerful tool for building, packaging, and versioning code from nearly any language, and covers countless complex use cases. So, the the documentation is invaluable. The Defining metadata (meta.yaml) page is especially handy. There's no way to avoid complexity in some cases, though using thesecondevtools and their associated conventions can often hide a lot of it. - You might like to create a single conda (e.g. Miniforge) installation that multiple users of a multi-tenant system can share. Beware: Many conda packages contain files that are group-writable, and if your shared conda installation is
chgrped to a group that your users are also members of, they may be able to, even if by accident, overwrite files that should be read-only to them. To work around this: 1. Make sure that the conda installation's files arechgrped to a group that only contains the appropriate user(s); and 2. Have each userexport CONDA_PKGS_DIR=$HOME/.conda/pkgsor similar. The first step ensures that arbitrary users cannot modify any part of the conda installation but, by itself, would also lead to errors when users ran commands likeconda create, as they would not have permissions to download package files into the conda directory, as conda requires. The second steps mitigates that issue, by letting each user download and cache packages in a directory they own -- at the unfortunate potential expense of added disk use. Used together, these steps support group use of a shared conda installation; but, if you can spare the disk space, consider having users maintain their own e.g. Miniconda installations, to avoid coordination headaches. - It may sometimes be useful to create multiple development environments concurrently from the "same" code, perhaps based on different git branches. This would normally cause a conflict, as the same development environment name (e.g.
DEV-hello) would be oversubscribed. You may set theDEV_ENV_PREFIXenvironment variable to change theDEVprefix to something else; for example,DEV_ENV_PREFIX=FOO make devshellwould create development environmentFOO-helloin thedemoproject. - Some of the techniques used in the
Makefile,recipe/directory, etc., of the top-levelcondevproject are not meant for duplication in projects that would use thecondevtools, but are required for bootstrapping in environments wherecondevtools are not yet available. The techniques used in thedemoproject are better examples to follow. - If
recipe/conda_build_config.yamlexists and defines multiple build variants,make devshellwill choose the first variant returned byconda-build'sapi.render()function to define the packages to be installed in and available to the development shell. This may or may not be what you want, so you may need to temporarily simplify this file until the variant you want to interact with in your development shell is the one you get.