Pipcl documentation

[This is all auto-generated from pipcl doc-comments using sphinx.ext.autodoc, sphinx.ext.autosummary and autodocsumm. ]

pipcl

Python packaging operations, including PEP-517 support, for use by a setup.py script.

Overview:

The intention is to take care of as many packaging details as possible so that setup.py contains only project-specific information, while also giving as much flexibility as possible.

For example we provide a function pipcl.build_extension() that can be used to build a SWIG extension, but we also give access to the located compiler/linker so that a setup.py script can take over the details itself.

Doctests:

Doctest strings are provided in some comments.

Test in the usual way with:

python -m doctest pipcl.py

Test specific functions/classes with:

python pipcl.py --doctest run_if ...

If no functions or classes are specified, this tests everything.

Graal:

For Graal we require that PIPCL_GRAAL_PYTHON is set to non-graal Python (we build for non-graal except with Graal Python’s include paths and library directory).

Classes:

LogPrefix(prefix)

A context manager to add a temporary prefix to log output.

NewFiles(glob_pattern[, recursive])

Detects new/modified/updated files matching a glob pattern.

Package(name, version, *[, author, ...])

Represents a Python package.

PythonFlags()

Compile/link flags for the current python, for example the include path needed to get Python.h.

Functions:

any_cpp(source_paths)

base_compiler([vs, pythonflags, cpp, use_env])

Returns basic compiler command and PythonFlags.

base_linker([vs, pythonflags, cpp, use_env])

Returns basic linker command.

build_extension(name, path_i, outdir, *[, ...])

Builds a Python extension module using SWIG.

compiler_command(*[, includes, defines, ...])

Returns generic compiler and linker commands.

cpu_bits()

Returns CPU wordsize in bits.

current_py_limited_api()

Returns value of PyLIMITED_API to build for current Python.

darwin()

Returns true of we are running on MacOS.

fs_ensure_dir(path)

Ensures directory <path> exists.

fs_ensure_empty_dir(path)

Ensures <path> is an empty directory.

fs_ensure_parent_dir(path)

Ensures parent directory of <path> exists.

fs_filesize(filename[, default])

Returns size of <filename> or <default> if error (e.g. doesn't exist).

fs_find_in_paths(name[, paths, verbose])

Looks for name in paths and returns complete path.

fs_read(path[, binary])

Returns string containing contents of file <path>.

fs_remove(path)

Removes file or directory, without raising exception if it doesn't exist.

fs_remove_dir_contents(path)

Removes all items in directory path; does not remove path itself.

fs_symlink(from_, to_)

Makes symbolic link from <from_> to <to_>.

fs_write(path, data[, binary])

Writes <data> to file <path>.

fs_write_key(path, data)

Writes <data> to <path>, ensuring that <path> is created with appropriate permissions.

get_soname(path)

If we are on Linux and path is softlink and points to a shared library for which objdump -p contains 'SONAME', return the pointee.

get_time_iso_8601(text)

Converts ISO-8601 text to Unix time.

git_get(local, *[, remote, branch, tag, ...])

Creates/updates local checkout <local> of remote repository and returns absolute path of <local>.

git_info(directory)

Returns (sha, comment, diff, branch), all items are str or None if not available.

git_info_author_date(directory)

Returns author date (as a Unix time) of HEAD.

git_info_committer_date(directory)

Returns committer date (as a Unix time) of HEAD.

git_info_py(path, *[, check, name_branch, ...])

Returns python code that defines variables that contain information about a git checkout, typically the checkout that is being used to build a package. :param path: Path of git checkout. :param check: If true we raise if we fail to get git info (e.g. <path> is not a Git checkout), otherwise the returned text sets all variables to None. :param name_branch: Name of variable containing git branch, or false to omit. :param name_comment: Name of variable containing git comment, or false to omit. :param name_diff: Name of variable containing output of git diff, or false to omit. :param name_diff_n: Name of variable containing length of output of git diff. :param name_sha: Name of variable containing git sha, or false to omit. :param prefix: Prefix of each variable name.

git_items(directory[, submodules])

Returns list of paths for all files known to git within a directory.

install_dir([root])

Returns install directory used by install().

linker_command(*[, libpaths, libs, ...])

Returns generic compiler and linker commands.

linux()

Returns true of we are running on Linux.

log([text, caller])

Writes <text> to log.

log0([text, caller])

log1([text, caller])

log2([text, caller])

log_prefix(pattern)

Sets log prefix pattern.

log_tee(path[, path_simple])

Copies log output to <path>.

macos_add_brew_path(package[, env, gnubin])

Adds path(s) for Brew <package>'s binaries to env['PATH'].

macos_add_cross_flags(command)

If running on MacOS and environment variables ARCHFLAGS is set (indicating we are cross-building, e.g. for arm64), returns command with extra flags appended.

macos_patch(library, *sublibraries)

If running on MacOS, patches library so that all references to items in sublibraries are changed to @rpath/{leafname}.

macos_patch2(path_so, libpaths, libs)

openbsd()

Returns true of we are running on OpenBSD.

pyodide()

Returns true of we are running on Pyodide.

python_version_tuple()

Like platform.python_version_tuple() except converts items to integers if possible.

relpath(path[, start, allow_up])

A safe alternative to os.path.relpath(), avoiding an exception on Windows if the drive needs to change - in this case we use os.path.abspath().

run(command, *[, capture, check, verbose, ...])

Runs a command using subprocess.run().

run_if(command, out, *prerequisites[, caller])

Runs a command only if the output file is not up to date.

show_sysconfig()

Shows contents of sysconfig.get_paths() and sysconfig.get_config_vars() dicts.

show_system()

Show useful information about the system plus argv and environ.

str_to_int(s[, replace])

Returns string converted to int, with configurable behaviour if the string is not convertible.

swig_get(swig, quick[, swig_local])

Returns <swig> or a new swig binary.

swig_prepare_build(swig_env_extra)

Builds required tools for building swig (not Windows).

sysconfig_python_flags()

Returns include paths and library directory for Python.

venv_enter_cmd(venv_name)

Returns command that will enter venv, different on Windows vs Unix.

verbose([level])

Sets verbose level if level is not None.

version_override(name)

If PIPCL_CHANGE_VERSIONS is set and contains a match for <name>, we return the override version.

version_to_tuple(version[, replace])

Returns a tuple containing each item from <version> converted to an int.

wasm()

Returns true of we are running on Wasm.

windows()

Returns true of we are running on Windows.

zipfile_writestr(zf, name, contents[, ...])

Alternative to zipfile.ZipFile.writestr(), which allows control over permissions.

class pipcl.LogPrefix(prefix)

A context manager to add a temporary prefix to log output.

class pipcl.NewFiles(glob_pattern, recursive=None)

Detects new/modified/updated files matching a glob pattern. Useful for detecting wheels created by pip or cubuildwheel etc.

Sets things up to look for new files matching <glob_pattern>. <recursive> is passed to glob.glob(), so for example set to true if using **.

Methods:

get()

Returns list of new matches of <glob_pattern> - paths of files that were not present previously, or have different mtimes or have different contents.

get_n(n)

Returns new files matching <glob_pattern>, asserting that there are exactly <n>.

get_one()

Returns new match of <glob_pattern>, asserting that there is exactly one.

get()

Returns list of new matches of <glob_pattern> - paths of files that were not present previously, or have different mtimes or have different contents.

get_n(n)

Returns new files matching <glob_pattern>, asserting that there are exactly <n>.

get_one()

Returns new match of <glob_pattern>, asserting that there is exactly one.

class pipcl.Package(
name,
version,
*,
author=None,
author_email=None,
classifier=None,
description=None,
description_content_type=None,
download_url=None,
home_page=None,
keywords=None,
license=None,
maintainer=None,
maintainer_email=None,
platform=None,
project_url=None,
provides_extra=None,
requires_dist=None,
requires_external=None,
requires_python=None,
summary=None,
supported_platform=None,
entry_points=None,
fn_build=None,
fn_clean=None,
fn_sdist=None,
pure=False,
py_limited_api=None,
tag_python=None,
tag_abi=None,
tag_platform=None,
root=None,
wheel_compression=8,
wheel_compresslevel=None,
)

Represents a Python package.

The constructor takes a definition of a Python package similar to that passed to distutils.core.setup() or setuptools.setup() (name, version, summary etc) plus callbacks for building, getting a list of sdist filenames, and cleaning.

Methods are provided that can be used to implement a Python package’s setup.py supporting PEP-517.

Basic command line handling is also supported for use with a legacy (pre-PEP-517) pip, as implemented by legacy distutils/setuptools and described in: https://pip.pypa.io/en/stable/reference/build-system/setup-py/

The file pyproject.toml must exist; this is checked if/when fn_build() is called.

Here is a doctest example of using pipcl to create a SWIG extension module called ‘foo’, with a console-script command line tool called cli. Requires swig.

Create an empty test directory:

>>> import os
>>> import shutil
>>> shutil.rmtree('pipcl_test', ignore_errors=1)
>>> os.mkdir('pipcl_test')

Create a setup.py which uses pipcl to define an extension module.

>>> import textwrap
>>> with open('pipcl_test/setup.py', 'w') as f:
...     _ = f.write(textwrap.dedent("""
...             import sys
...             import pipcl
...
...             def build():
...                 so_leaf = pipcl.build_extension(
...                         name = 'foo',
...                         path_i = 'foo.i',
...                         outdir = 'build',
...                         source_extra = 'wibble.c',
...                         )
...                 return [
...                         ('build/foo.py', 'foo/__init__.py'),
...                         ('cli.py', 'foo/__main__.py'),
...                         (f'build/{so_leaf}', f'foo/'),
...                         (b'Hello world', 'foo/hw.txt'),
...                         ]
...
...             def sdist():
...                 return [
...                         'foo.i',
...                         'bar.i',
...                         'wibble.c',
...                         'setup.py',
...                         'pipcl.py',
...                         'pyproject.toml',
...                         'wdev.py',
...                         'README',
...                         ]
...
...             p = pipcl.Package(
...                     name = 'foo',
...                     version = '1.2.3',
...                     description = 'README',
...                     fn_build = build,
...                     fn_sdist = sdist,
...                     entry_points = (
...                         { 'console_scripts': [
...                             'foo_cli = foo.__main__:main',
...                             ],
...                         }),
...                     )
...
...             build_wheel = p.build_wheel
...             build_sdist = p.build_sdist
...
...             # Handle old-style setup.py command-line usage:
...             if __name__ == '__main__':
...                 p.handle_argv(sys.argv)
...             """))

Create the files required by the above setup.py - pyproject.toml, the SWIG .i input file, the README file, and copies of pipcl.py and wdev.py.

>>> with open('pipcl_test/pyproject.toml', 'w') as f:
...     _ = f.write(textwrap.dedent("""
...             [build-system]
...             requires = []
...
...             # See pep-517.
...             #
...             build-backend = "setup"
...             backend-path = ["."]
...             """))
>>> with open('pipcl_test/foo.i', 'w') as f:
...     _ = f.write(textwrap.dedent("""
...             %include bar.i
...             %{
...             #include <stdio.h>
...             #include <string.h>
...             int bar(const char* text)
...             {
...                 printf("bar(): text: %s\\n", text);
...                 int len = (int) strlen(text);
...                 printf("bar(): len=%i\\n", len);
...                 fflush(stdout);
...                 return len;
...             }
...             %}
...             int bar(const char* text);
...             """))
>>> with open('pipcl_test/bar.i', 'w') as f:
...     _ = f.write( '\n')
>>> with open('pipcl_test/wibble.c', 'w') as f:
...     _ = f.write( '\n')
>>> with open('pipcl_test/README', 'w') as f:
...     _ = f.write(textwrap.dedent("""
...             This is Foo.
...             """))
>>> with open('pipcl_test/cli.py', 'w') as f:
...     _ = f.write(textwrap.dedent("""
...             def main():
...                 print('pipcl_test:main().')
...             if __name__ == '__main__':
...                 main()
...             """))
>>> root = os.path.dirname(__file__)
>>> _ = shutil.copy2(f'{root}/pipcl.py', 'pipcl_test/pipcl.py')
>>> _ = shutil.copy2(f'{root}/wdev.py', 'pipcl_test/wdev.py')

Use setup.py’s command-line interface to build and install the extension module into root pipcl_test/install.

>>> _ = subprocess.run(
...         f'cd pipcl_test && {sys.executable} setup.py --root install install',
...         shell=1, check=1)

The actual install directory depends on sysconfig.get_path('platlib'):

>>> if windows():
...     install_dir = 'pipcl_test/install'
... else:
...     install_dir = f'pipcl_test/install/{sysconfig.get_path("platlib").lstrip(os.sep)}'
>>> assert os.path.isfile( f'{install_dir}/foo/__init__.py')

Create a test script which asserts that Python function call foo.bar(s) returns the length of s, and run it with PYTHONPATH set to the install directory:

>>> with open('pipcl_test/test.py', 'w') as f:
...     _ = f.write(textwrap.dedent("""
...             import sys
...             import foo
...             text = 'hello'
...             print(f'test.py: calling foo.bar() with text={text!r}')
...             sys.stdout.flush()
...             l = foo.bar(text)
...             print(f'test.py: foo.bar() returned: {l}')
...             assert l == len(text)
...             """))
>>> r = subprocess.run(
...         f'{sys.executable} pipcl_test/test.py',
...         shell=1, check=1, text=1,
...         stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
...         env=os.environ | dict(PYTHONPATH=install_dir),
...         )
>>> print(r.stdout)
test.py: calling foo.bar() with text='hello'
bar(): text: hello
bar(): len=5
test.py: foo.bar() returned: 5

Check that building sdist and wheel succeeds. For now we don’t attempt to check that the sdist and wheel actually work.

>>> _ = subprocess.run(
...         f'cd pipcl_test && {sys.executable} setup.py sdist',
...         shell=1, check=1)
>>> _ = subprocess.run(
...         f'cd pipcl_test && {sys.executable} setup.py bdist_wheel',
...         shell=1, check=1)

Check that rebuild does nothing.

>>> t0 = os.path.getmtime('pipcl_test/build/foo.py')
>>> _ = subprocess.run(
...         f'cd pipcl_test && {sys.executable} setup.py bdist_wheel',
...         shell=1, check=1)
>>> t = os.path.getmtime('pipcl_test/build/foo.py')
>>> assert t == t0

Check that touching bar.i forces rebuild.

>>> os.utime('pipcl_test/bar.i')
>>> _ = subprocess.run(
...         f'cd pipcl_test && {sys.executable} setup.py bdist_wheel',
...         shell=1, check=1)
>>> t = os.path.getmtime('pipcl_test/build/foo.py')
>>> assert t > t0

Check that touching foo.i.cpp does not run swig, but does recompile/link.

>>> so_suffix = 'pyd' if windows() else 'so'
>>> t0 = time.time()
>>> os.utime('pipcl_test/build/foo.i.cpp')
>>> _ = subprocess.run(
...         f'cd pipcl_test && {sys.executable} setup.py bdist_wheel',
...         shell=1, check=1)
>>> assert os.path.getmtime('pipcl_test/build/foo.py') <= t0
>>> so = glob.glob(f'pipcl_test/build/*.{so_suffix}')
>>> assert len(so) == 1, f'{so_suffix=} {so=} {os.path.abspath(".")=}'
>>> so = so[0]
>>> assert os.path.getmtime(so) > t0

Check that touching wibbble.c does not run swig, but does recompile/link.

>>> t0 = time.time()
>>> os.utime('pipcl_test/wibble.c')
>>> _ = subprocess.run(
...         f'cd pipcl_test && {sys.executable} setup.py bdist_wheel',
...         shell=1, check=1)
>>> assert os.path.getmtime('pipcl_test/build/foo.py') <= t0
>>> so = glob.glob(f'pipcl_test/build/*.{so_suffix}')
>>> assert len(so) == 1
>>> so = so[0]
>>> assert os.path.getmtime(so) > t0

Check entry_points causes creation of command foo_cli when we install from our wheel using pip. [As of 2024-02-24 using pipcl’s CLI interface directly with setup.py install does not support entry points.]

>>> print('Creating venv.', file=sys.stderr)
>>> _ = subprocess.run(
...         f'cd pipcl_test && {sys.executable} -m venv pylocal',
...         shell=1, check=1)
>>> print('Installing from wheel into venv using pip.', file=sys.stderr)
>>> whl = glob.glob(f'pipcl_test/dist/*.whl')[0]
>>> _ = subprocess.run(f'{venv_enter_cmd("pipcl_test/pylocal")} && pip install {whl}',
...         shell=1, check=1)
>>> print('Running foo_cli.', file=sys.stderr)
>>> _ = subprocess.run(
...         f'{venv_enter_cmd("pipcl_test/pylocal")} && foo_cli',
...         shell=1, check=1)

Check that wheel contents’ unix permissions are readable by all.

>>> wheel_path = glob.glob('pipcl_test/dist/*.whl')[0]
>>> with zipfile.ZipFile(wheel_path) as z:
...     for zi in z.infolist():
...         external_attr_lo = zi.external_attr & 15
...         external_attr_hi = zi.external_attr >> 16
...         assert external_attr_lo + (external_attr_hi << 16) == zi.external_attr
...         assert external_attr_hi & 0o444 == 0o444, (
...                 f'info.external_attr not readable by all:'
...                 f' {zi.external_attr=:#x}'
...                 f' {external_attr_hi=:#o}'
...                 f' {external_attr_lo=:#o}'
...                 f' {zi.filename=}'
...                 )

Wheels and sdists

Wheels:

We generate wheels according to: https://packaging.python.org/specifications/binary-distribution-format/

  • {name}-{version}.dist-info/RECORD uses sha256 hashes.

  • Other RECORD* files such as RECORD.jws or RECORD.p7s are not generated.

  • {name}-{version}.dist-info/WHEEL has:

    • Wheel-Version: 1.0

    • Root-Is-Purelib: false

  • No support for signed wheels.

Sdists:

We generate sdist’s according to: https://packaging.python.org/specifications/source-distribution-format/

The initial args before entry_points define the package metadata and closely follow the definitions in: https://packaging.python.org/specifications/core-metadata/

Parameters:
  • name – Used for metadata Name. A string, the name of the Python package.

  • version

    Used for metadata Version. A string, the version of the Python package. Also see PEP-440 Version Identification and Dependency Specification.

    Special handling of PIPCL_CHANGE_VERSIONS:

    If environment variable PIPCL_CHANGE_VERSIONS is set, it is used to override version. PIPCL_CHANGE_VERSIONS should contain one or more lines matching:

    <package-name-regex> <new-version>

    If any line’s <package-name-regex> matches <name>, then <version> is changed to the line’s <new-version>.

    For example if foo/ contains a package called foo that uses pipcl, then this:

    $ export PIPCL_CHANGE_VERSIONS=’ > ^foo$ 1.2.3.4 > ^bar$ 3.4.5 ‘ $ pip wheel foo/ … $

    • Will create a foo wheel with version 1.2.3.4, regardless of what version is specified in foo/setup.py.

    • Any dependency of package foo on package bar, will be changed to bar==3.4.5.

    Note that if <package-name-regex> is not in the form ^...$, it may match extra unintended package names.

  • author – Used for metadata Author. Author name.

  • author_email – Used for metadata Author-email. Author email.

  • classifier

    Used for metadata Classifier. A string or list of strings. Also see:

  • description – Used for metadata Description. A string. If contains newlines, a detailed description of the package. Otherwise the path of a file containing the detailed description of the package.

  • description_content_type – Used for metadata Description-Content-Type. A string describing markup of description arg. For example text/markdown; variant=GFM. If not specified we try to infer a value from <description> file suffix.

  • download_url – Used for metadata Download-URL. Where this version can be downloaded from. Deprecated since metadata-1.2 - use project_url.

  • home_page – Used for metadata Home-page. URL of home page. Deprecated since metadata-1.2 - use project_url.

  • keywords – Used for metadata Keywords. A string containing comma-separated keywords.

  • license – Used for metadata License. A string containing the license text. Written into metadata file COPYING. Is also written into metadata itself if not multi-line.

  • maintainer – Used for metadata Maintainer. Maintainer.

  • maintainer_email – Used for metadata Maintainer-email. Maintainer email.

  • platform – Used for metadata Platform. A string or list of strings.

  • project_url – Used for metadata Project-URL. A string or list of strings, each of the form: {name}, {url}. Well-known names include: homepage, source, documentation, issues; for more details, see: https://packaging.python.org/en/latest/specifications/well-known-project-urls/#well-known-project-urls

  • provides_extra – Used for metadata Provides-Extra. A string or list of strings.

  • requires_dist

    Used for metadata Requires-Dist. A string or list of strings, Python packages required at runtime. None items are ignored.

    If environment variable PIPCL_CHANGE_VERSIONS is set, version numbers for matching items in <requires_dist> will be overridden - see description of <version> for details. (This will overwrite matching requirements to be in the simple form <name>==; any other elements will be lost.)

  • requires_external – Used for metadata Requires-External. A string or list of strings.

  • requires_python – Used for metadata Requires-Python. A string or list of strings.

  • summary – Used for metadata Summary. A string, short description of the package.

  • supported_platform – Used for metadata Supported-Platform. A string or list of strings.

  • entry_points

    A string specifying *.dist-info/entry_points.txt, for example:

    [console_scripts]
    foo_cli = foo.__main__:main
    

    See: https://packaging.python.org/en/latest/specifications/entry-points/

    Alternatively one can specify a dict similar to that supported by setuptools (see https://setuptools.pypa.io/en/latest/userguide/entry_point.html):

    {
        'console_scripts':
        [
            'foo_cli = foo.__main__:main',
        ],
    }
    

  • fn_build

    A function taking no args, or a single config_settings dict arg (as described in PEP-517). This should build all required files and return a list of items; each item should be a tuple (from_, to_), or a single string path which is treated as the tuple (path, path).

    from_ can be a string or a bytes. If a string it should be the path to a file; a relative path is treated as relative to root. If a bytes it is the contents of the file to be added.

    to_ identifies what the file should be called within a wheel or when installing. If to_ is empty or / we set it to the leaf of from_ (from_ must not be a bytes) - i.e. we place the file in the root directory of the wheel; otherwise if to_ ends with / the leaf of from_ is appended to it (and from_ must not be a bytes).

    Initial $dist-info/ in _to is replaced by {name}-{version}.dist-info/; this is useful for license files etc.

    Initial $data/ in _to is replaced by {name}-{version}.data/. We do not enforce particular subdirectories, instead it is up to fn_build() to specify specific subdirectories such as purelib, headers, scripts, data etc.

    If we are building a wheel (e.g. python setup.py bdist_wheel, or PEP-517 pip calls self.build_wheel()), we add file from_ to the wheel archive with name to_.

    If we are installing (e.g. install command in the argv passed to self.handle_argv()), then we copy from_ to {sitepackages}/{to_}, where sitepackages is the installation directory, the default being sysconfig.get_path('platlib') e.g. myvenv/lib/python3.9/site-packages/.

    When calling this function, we assert that the file pyproject.toml exists in the current directory. (We do this here rather than in pipcl.Package’s constructor, as otherwise importing setup.py from non-package-related code could fail.)

  • fn_clean

    A function taking a single arg all_ that cleans generated files. all_ is true iff --all is in argv.

    For safety and convenience, can also returns a list of files/directory paths to be deleted. Relative paths are interpreted as relative to root. All paths are asserted to be within root.

  • fn_sdist

    A function taking no args, or a single config_settings dict arg (as described in PEP517), that returns a list of items to be copied into the sdist. The list should be in the same format as returned by fn_build.

    It can be convenient to use pipcl.git_items().

    The specification for sdists requires that the list contains pyproject.toml; we enforce this with a Python assert.

  • pure – If true we mark wheels as pure python - for example default wheel tag will be py3-none-any.

  • py_limited_api – If true we build extension modules that use the Python Limited API. We use the version of sys.executable to define Py_LIMITED_API when compiling extensions, and use ABI tag abi3 in the wheel name if argument tag_abi is None.

  • tag_python – First element of wheel tag defined in PEP-425. If None we use cp{version}.

  • tag_abi – Second element of wheel tag defined in PEP-425. If None we use none.

  • tag_platform – Third element of wheel tag defined in PEP-425. Default is os.environ('AUDITWHEEL_PLAT') if set, otherwise derived from sysconfig.get_platform() (was setuptools.distutils.util.get_platform(), before that distutils.util.get_platform() as specified in the PEP), e.g. openbsd_7_0_amd64.

  • root – Root of package, defaults to current directory.

  • wheel_compression – Used as zipfile.ZipFile()’s compression parameter when creating wheels.

Occurrences of None in lists are ignored.

Methods:

build_sdist(sdist_directory, formats[, ...])

A PEP-517 build_sdist() function.

build_wheel(wheel_directory[, ...])

A PEP-517 build_wheel() function.

handle_argv(argv)

Attempt to handle old-style (pre PEP-517) command line passed by old releases of pip to a setup.py script, and manual running of setup.py.

install([record_path, root])

Called by handle_argv() to handle install command..

tag_abi()

ABI tag.

tag_platform()

Find platform tag used in wheel filename.

tag_python()

Get two-digit python version, e.g. 'cp3.8' for python-3.8.6.

wheel_name()

wheel_name_match(wheel)

Returns true if wheel matches our wheel.

wheel_tag_string()

Returns <tag_python>-<tag_abi>-<tag_platform>.

build_sdist(
sdist_directory,
formats,
config_settings=None,
)

A PEP-517 build_sdist() function.

Also called by handle_argv() to handle the sdist command.

Returns leafname of generated archive within sdist_directory.

build_wheel(
wheel_directory,
config_settings=None,
metadata_directory=None,
)

A PEP-517 build_wheel() function.

Also called by handle_argv() to handle the bdist_wheel command.

Returns leafname of generated wheel within wheel_directory.

handle_argv(argv)

Attempt to handle old-style (pre PEP-517) command line passed by old releases of pip to a setup.py script, and manual running of setup.py.

This is partial support at best.

install(
record_path=None,
root=None,
)

Called by handle_argv() to handle install command..

tag_abi()

ABI tag.

tag_platform()

Find platform tag used in wheel filename.

tag_python()

Get two-digit python version, e.g. ‘cp3.8’ for python-3.8.6.

wheel_name()
wheel_name_match(wheel)

Returns true if wheel matches our wheel. We basically require the name to be the same, except that we accept platform tags that contain extra items (see pep-0600/), for example we return true with:

self: foo-cp38-none-manylinux2014_x86_64.whl wheel: foo-cp38-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl

wheel_tag_string()

Returns <tag_python>-<tag_abi>-<tag_platform>.

class pipcl.PythonFlags

Compile/link flags for the current python, for example the include path needed to get Python.h.

The ‘PIPCL_PYTHON_CONFIG’ environment variable allows to override the location of the python-config executable.

Members:
.includes:

String containing compiler flags for include paths.

.ldflags:

String containing linker flags for library paths.

pipcl.any_cpp(source_paths)
pipcl.base_compiler(
vs=None,
pythonflags=None,
cpp=False,
use_env=True,
)

Returns basic compiler command and PythonFlags.

Parameters:
  • vs – Windows only. A wdev.WindowsVS instance or None to use default wdev.WindowsVS instance.

  • pythonflags – A pipcl.PythonFlags instance or None to use default pipcl.PythonFlags instance.

  • cpp – If true we return C++ compiler command instead of C. On Windows this has no effect - we always return cl.exe.

  • use_env – If true we return ‘$CC’ or ‘$CXX’ if the corresponding environmental variable is set (without evaluating with os.getenv() or os.environ).

Returns (cc, pythonflags):
cc:

C or C++ command. On Windows this is of the form {vs.vcvars}&&{vs.cl}; otherwise it is typically cc or c++.

pythonflags:

The pythonflags arg or a new pipcl.PythonFlags instance.

pipcl.base_linker(
vs=None,
pythonflags=None,
cpp=False,
use_env=True,
)

Returns basic linker command.

Parameters:
  • vs – Windows only. A wdev.WindowsVS instance or None to use default wdev.WindowsVS instance.

  • pythonflags – A pipcl.PythonFlags instance or None to use default pipcl.PythonFlags instance.

  • cpp – If true we return C++ linker command instead of C. On Windows this has no effect - we always return link.exe.

  • use_env – If true we use os.environ['LD'] if set.

Returns (linker, pythonflags):
linker:

Linker command. On Windows this is of the form {vs.vcvars}&&{vs.link}; otherwise it is typically cc or c++.

pythonflags:

The pythonflags arg or a new pipcl.PythonFlags instance.

pipcl.build_extension(
name,
path_i,
outdir,
*,
builddir=None,
includes=None,
defines=None,
libpaths=None,
libs=None,
optimise=True,
debug=False,
compiler_extra='',
compiler_extra_cpp='',
linker_extra='',
swig=None,
cpp=True,
source_extra=None,
prerequisites_swig=None,
prerequisites_compile=None,
prerequisites_link=None,
infer_swig_includes=True,
py_limited_api=False,
nogil=False,
)

Builds a Python extension module using SWIG. Works on Windows, Linux, MacOS and OpenBSD.

On Unix, sets rpath when linking shared libraries.

Parameters:
  • name – Name of generated extension module.

  • path_i – Path of input SWIG .i file. Internally we use swig to generate a corresponding .c or .cpp file.

  • outdir

    Output directory for generated files:

    • {outdir}/{name}.py

    • {outdir}/_{name}.so # Unix

    • {outdir}/_{name}.*.pyd # Windows

    We return the leafname of the .so or .pyd file.

  • builddir – Where to put intermediate files, for example the .cpp file generated by swig and .d dependency files. Default is outdir.

  • includes – A string, or a sequence of extra include directories to be prefixed with -I.

  • defines – A string, or a sequence of extra preprocessor defines to be prefixed with -D.

  • libpaths – A string, or a sequence of library paths to be prefixed with /LIBPATH: on Windows or -L on Unix.

  • libs – A string, or a sequence of library names. Each item is prefixed with -l on non-Windows.

  • optimise – Whether to use compiler optimisations and define NDEBUG.

  • debug – Whether to build with debug symbols.

  • compiler_extra – Extra compiler flags. Can be None.

  • compiler_extra_cpp – Extra c++ compiler flags. Can be None.

  • linker_extra – Extra linker flags. Can be None.

  • swig – Swig command; if false we use ‘swig’.

  • cpp – If true we tell SWIG to generate C++ code instead of C.

  • source_extra – Extra source files to build into the shared library,

  • prerequisites_swig

  • prerequisites_compile

  • prerequisites_link

    [These are mainly for use on Windows. On other systems we automatically generate dynamic dependencies using swig/compile/link commands’ -MD and -MF args.]

    Sequences of extra input files/directories that should force running of swig, compile or link commands if they are newer than any existing generated SWIG .i file, compiled object file or shared library file.

    If present, the first occurrence of True or False forces re-run or no re-run. Any occurrence of None is ignored. If an item is a directory path we look for newest file within the directory tree.

    If not a sequence, we convert into a single-item list.

    prerequisites_swig

    We use swig’s -MD and -MF args to generate dynamic dependencies automatically, so this is not usually required.

    prerequisites_compile prerequisites_link

    On non-Windows we use cc’s -MF and -MF args to generate dynamic dependencies so this is not usually required.

  • infer_swig_includes – If true, we extract -I and -I args from compile_extra (also /I on windows) and use them with swig so that it can see the same header files as C/C++. This is useful when using enviromment variables such as CC and CXX to set compile_extra.

  • py_limited_api

    If true we build for current Python’s limited API / stable ABI.

    Note that we will assert false if this extension is added to a pipcl.Package that has a different <py_limited_api>, because on Windows importing a non-py_limited_api extension inside a py_limited=True package fails.

  • nogil – If true we use swig’s -nogil flag which marks the extension as being thread safe.

Returns the leafname of the generated library file within outdir, e.g. _{name}.so on Unix or _{name}.cp311-win_amd64.pyd on Windows.

pipcl.compiler_command(
*,
includes=None,
defines=None,
optimise=True,
debug=False,
compiler_extra='',
compiler_extra_cpp='',
cpp=None,
python=False,
py_limited_api=False,
rpath=False,
source_paths=None,
path_o=None,
)

Returns generic compiler and linker commands. Used by build_extension().

Parameters:
  • includes – A string, or a sequence of extra include directories to be prefixed with -I.

  • defines – A string, or a sequence of extra preprocessor defines to be prefixed with -D.

  • optimise – Whether to use compiler optimisations and define NDEBUG.

  • debug – Whether to build with debug symbols.

  • compiler_extra – Extra compiler flags. Can be None.

  • compiler_extra – Extra c++ compiler flags. Can be None.

  • cpp – If true we tell SWIG to generate C++ code instead of C. If None we use suffix of items in <source_paths>.

  • python

    If true: * We include flags for using Python headers and libraries. * We predefine Py_GIL_DISABLED if

    sysconfig.get_config_var(‘Py_GIL_DISABLED’)==1 (i.e. free-thread build).

  • py_limited_api

    If true we build for current Python’s limited API / stable ABI.

    Note that we will assert false if this extension is added to a pipcl.Package that has a different <py_limited_api>, because on Windows importing a non-py_limited_api extension inside a py_limited=True package fails.

pipcl.cpu_bits()

Returns CPU wordsize in bits.

pipcl.current_py_limited_api()

Returns value of PyLIMITED_API to build for current Python.

pipcl.darwin()

Returns true of we are running on MacOS.

pipcl.fs_ensure_dir(path)

Ensures directory <path> exists.

pipcl.fs_ensure_empty_dir(path)

Ensures <path> is an empty directory.

pipcl.fs_ensure_parent_dir(path)

Ensures parent directory of <path> exists.

pipcl.fs_filesize(filename, default=0)

Returns size of <filename> or <default> if error (e.g. doesn’t exist).

pipcl.fs_find_in_paths(
name,
paths=None,
verbose=False,
)

Looks for name in paths and returns complete path. paths is list/tuple or os.pathsep-separated string; if None we use $PATH. If name contains /, we return name itself if it is a file or None, regardless of <paths>.

pipcl.fs_read(path, binary=False)

Returns string containing contents of file <path>. Returns bytes if <binary> is true.

pipcl.fs_remove(path)

Removes file or directory, without raising exception if it doesn’t exist.

We assert-fail if the path still exists when we return, in case of permission problems etc.

pipcl.fs_remove_dir_contents(path)

Removes all items in directory path; does not remove path itself.

Makes symbolic link from <from_> to <to_>.

pipcl.fs_write(path, data, binary=False)

Writes <data> to file <path>.

pipcl.fs_write_key(path, data)

Writes <data> to <path>, ensuring that <path> is created with appropriate permissions.

pipcl.get_soname(path)

If we are on Linux and path is softlink and points to a shared library for which objdump -p contains ‘SONAME’, return the pointee. Otherwise return path. Useful if Linux shared libraries have been created with -Wl,-soname,..., where we need to embed the versioned library.

pipcl.get_time_iso_8601(text)

Converts ISO-8601 text to Unix time.

pipcl.git_get(
local,
*,
remote=None,
branch=None,
tag=None,
sha=None,
text=None,
depth=1,
env_extra=None,
update=True,
submodules=True,
devel=1,
key=None,
keyfile=None,
clone_extra=None,
)

Creates/updates local checkout <local> of remote repository and returns absolute path of <local>.

If <text> is set but does not start with ‘git:’, it is assumed to be an up to date local checkout, and we return absolute path of <text> without doing any git operations.

Parameters:
  • local – Local directory. Created and/or updated using git clone and git fetch etc.

  • remote – Remote git repostitory, for example ‘https://github.com/ArtifexSoftware/mupdf.git’. Can be overridden by <text>.

  • branch – Branch to use; can be overridden by <text>.

  • tag – Tag to use; can be overridden by <text>.

  • sha – Use this sha; must be the full length sha, e.g. 5ea4449c3edba8840d5c27e15095536cbfc7c1f

  • text

    If None or empty:

    Ignored.

    If starts with ‘git:’:

    The remaining text should be a command-line style string containing some or all of these args:

    -b|–branch <branch> -t|-tag <tag> –depth <depth> <remote>

    These overrides <branch>, <tag>, <remote> and <depth>.

    Otherwise:

    <text> is assumed to be a local directory, and we simply return it as an absolute path without doing any git operations.

    For example these all clone/update/branch master of https://foo.bar/qwerty.git to local checkout ‘foo-local’:

    git_get(‘foo-local’, remote=’https://foo.bar/qwerty.git’, branch=’master’) git_get(‘foo-local’, text=’git:–branch master https://foo.bar/qwerty.git’) git_get(‘foo-local’, text=’git:–branch master’, remote=’https://foo.bar/qwerty.git’) git_get(‘foo-local’, text=’git:’, branch=’master’, remote=’https://foo.bar/qwerty.git’)

  • depth – Depth of local checkout when cloning and fetching, or None.

  • env_extra – Dict of extra name=value environment variables to use whenever we run git.

  • update – If false we do not update existing repository. Might be useful if testing without network access.

  • submodules – If true, we clone with --recursive --shallow-submodules and run git submodule update --init --recursive before returning.

  • key – Ssh key to use.

  • keyfile – Ssh key file to use. Only used if <key> is None.

  • clone_extra – If true, is added to any git clone command. For example clone_extra='--config core.autocrlf=input' to avoid converting text files to Windows line endings.

pipcl.git_info(directory)

Returns (sha, comment, diff, branch), all items are str or None if not available. <comment> is the first line of the commit message.

directory:

Root of git checkout.

pipcl.git_info_author_date(directory)

Returns author date (as a Unix time) of HEAD.

pipcl.git_info_committer_date(directory)

Returns committer date (as a Unix time) of HEAD.

pipcl.git_info_py(
path,
*,
check=True,
name_branch='branch',
name_comment='comment',
name_diff='diff',
name_diff_n='diff_n',
name_sha='sha',
prefix='git_',
)

Returns python code that defines variables that contain information about a git checkout, typically the checkout that is being used to build a package. :param path: Path of git checkout. :param check: If true we raise if we fail to get git info (e.g. <path> is not a

Git checkout), otherwise the returned text sets all variables to None.

Parameters:
  • name_branch – Name of variable containing git branch, or false to omit.

  • name_comment – Name of variable containing git comment, or false to omit.

  • name_diff – Name of variable containing output of git diff, or false to omit.

  • name_diff_n – Name of variable containing length of output of git diff.

  • name_sha – Name of variable containing git sha, or false to omit.

  • prefix – Prefix of each variable name.

Example output:

git_branch = ‘main’ comment = ‘Add wibble’ git_diff = ‘’ git_diff_n = 0 git_sha = ‘aa00d79773e1e3c4176b59ac76bfef89830bd289’

pipcl.git_items(directory, submodules=False)

Returns list of paths for all files known to git within a directory.

Parameters:
  • directory – Must be somewhere within a git checkout.

  • submodules – If true we also include git submodules.

Returns:

A list of paths for all files known to git within directory. Each path is relative to directory. directory must be somewhere within a git checkout.

We run a git ls-files command internally.

This function can be useful for the fn_sdist() callback.

pipcl.install_dir(root=None)

Returns install directory used by install().

This will be sysconfig.get_path('platlib'), modified by root if not None.

pipcl.linker_command(
*,
libpaths=None,
libs=None,
optimise=True,
debug=False,
linker_extra='',
cpp=True,
python=False,
py_limited_api=False,
rpath=False,
path_os=None,
path_out=None,
)

Returns generic compiler and linker commands. Used by build_extension().

Parameters:
  • libpaths – A string, or a sequence of library paths to be prefixed with /LIBPATH: on Windows or -L on Unix.

  • libs – A string, or a sequence of library names. Each item is prefixed with -l on non-Windows.

  • optimise – Whether to use compiler optimisations and define NDEBUG.

  • debug – Whether to build with debug symbols.

  • linker_extra – Extra linker flags. Can be None.

  • cpp

    .

  • python – If true we include flags for using Python headers and libraries.

  • py_limited_api

    If true we build for current Python’s limited API / stable ABI.

    Note that we will assert false if this extension is added to a pipcl.Package that has a different <py_limited_api>, because on Windows importing a non-py_limited_api extension inside a py_limited=True package fails.

  • rpath – If true we include an rpath arg to linker command, and patch up libraries on MacOS.

pipcl.linux()

Returns true of we are running on Linux.

pipcl.log(text='', caller=1)

Writes <text> to log. Caller identifies the frame to use if file:line is included in prefix; default 1 identifies the caller.

pipcl.log0(text='', caller=1)
pipcl.log1(text='', caller=1)
pipcl.log2(text='', caller=1)
pipcl.log_prefix(pattern)

Sets log prefix pattern.

pipcl.log_tee(path, path_simple=None)

Copies log output to <path>. If path_simple is specified, on exit we make a convenience symlink from <path_simple> to <path>.

pipcl.macos_add_brew_path(
package,
env=None,
gnubin=True,
)

Adds path(s) for Brew <package>’s binaries to env[‘PATH’].

We assert-fail if the relevant directory does no exist.

Parameters:
  • package – Name of package. We get <package_root> of installed package by running brew --prefix.

  • env – The environment dict to modify. If None we use os.environ. If PATH is not in <env>, we first copy os.environ[‘PATH’] into <env>.

  • gnubin – If true, we also add path to gnu binaries if it exists, <package_root>/libexe/gnubin.

pipcl.macos_add_cross_flags(command)

If running on MacOS and environment variables ARCHFLAGS is set (indicating we are cross-building, e.g. for arm64), returns command with extra flags appended. Otherwise returns unchanged command.

pipcl.macos_patch(library, *sublibraries)

If running on MacOS, patches library so that all references to items in sublibraries are changed to @rpath/{leafname}. Does nothing on other platforms.

library:

Path of shared library.

sublibraries:

List of paths of shared libraries; these have typically been specified with -l when library was created.

pipcl.macos_patch2(path_so, libpaths, libs)
pipcl.openbsd()

Returns true of we are running on OpenBSD.

pipcl.pyodide()

Returns true of we are running on Pyodide.

pipcl.python_version_tuple()

Like platform.python_version_tuple() except converts items to integers if possible.

pipcl.relpath(path, start=None, allow_up=True)

A safe alternative to os.path.relpath(), avoiding an exception on Windows if the drive needs to change - in this case we use os.path.abspath().

Parameters:
  • path – Path to be processed.

  • start – Start directory or current directory if None.

  • allow_up – If false we return absolute path is <path> is not within <start>.

pipcl.run(
command,
*,
capture=False,
check=1,
verbose=1,
env=None,
env_extra=None,
timeout=None,
caller=1,
prefix=None,
encoding=None,
errors='backslashreplace',
ticker=0,
out=None,
tee=None,
log=None,
)

Runs a command using subprocess.run().

Parameters:
  • command

    A string command to run or list of string args to run.

    If a string:

    Multiple lines in command are treated as a single command.

    • If a line starts with # it is discarded.

    • If a line contains ` #`, the trailing text is discarded.

    When running the command on Windows, newlines are replaced by spaces; otherwise each line is terminated by a backslash character.

  • capture – If true, we include the command’s output in our return value.

  • check – If true we raise an exception on error; otherwise we include the command’s returncode in our return value.

  • verbose – If true we show the command.

  • env – None or dict to use instead of <os.environ>.

  • env_extra – None or dict to add to <os.environ> or <env>.

  • timeout – If not None, timeout in seconds; passed directly to subprocess.run(). Note that on MacOS subprocess.run() seems to leave processes running if timeout expires.

  • prefix

    String prefix for each line of output.

    If true: * We run command with stdout=subprocess.PIPE and

    stderr=subprocess.STDOUT, repetaedly reading the command’s output and writing it with <prefix>.

    • We do not support <timeout>, which must be None.

  • encoding

  • errors – Encoding of child process output.

  • ticker – If non-zero, we show a rotating bar when waiting for child process output, with <ticker> seconds between each rotation.

  • out – Where to write output. Single item or list/tuple of items. Each item can be a callable or an object with .write() and .flush() members. Special value ‘log’ writes to pipcl.log().

  • tee – Name of file to also write to. We refuse to overwrite existing file.

  • log – If true we write to pipcl.log(). This is the default.

On Windows:

<ticker> is not supported and ignored. <prefix> and <timeout> both being true is not supported, and will raise an exception. These restrictions are because the Python module selectors does not handle pipes.

Returns:

false false returncode false true (returncode, output) true false None or raise exception true true output or raise exception

pipcl.run_if(
command,
out,
*prerequisites,
caller=1,
)

Runs a command only if the output file is not up to date.

Parameters:
  • command – The command to run. We write this and a hash of argv[0] into a file <out>.cmd so that we know to run a command if the command itself has changed.

  • out – Path of the output file.

  • prerequisites – List of prerequisite paths or true/false/None items. If an item is None it is ignored, otherwise if an item is not a string we immediately return it cast to a bool. We recurse into directories, effectively using the newest file in the directory.

Returns:

True if we ran the command, otherwise None.

If the output file does not exist, the command is run:

>>> del _log_f[:]
>>> _log_f.append(sys.stdout)
>>> log_prefix('%f(): ')
>>> out = 'tests/run_if_test_out'
>>> fs_remove(out)
>>> fs_remove( f'{out}.cmd')
>>> run_if( f'touch {out}', out, caller=0)
run_if(): Running command because: File does not exist: 'tests/run_if_test_out'
run_if(): Running: touch tests/run_if_test_out
True

If we repeat, the output file will be up to date so the command is not run:

>>> run_if( f'touch {out}', out, caller=0)
run_if(): Not running command because up to date: 'tests/run_if_test_out'

If we change the command, the command is run:

>>> run_if( f'touch {out};', out, caller=0)
run_if(): Running command because: Command has changed:
run_if():     @@ -1,2 +1,2 @@
run_if():      touch
run_if():     -tests/run_if_test_out
run_if():     +tests/run_if_test_out;
run_if(): Running: touch tests/run_if_test_out;
True

If we add a prerequisite that is newer than the output, the command is run:

>>> time.sleep(1)
>>> prerequisite = 'tests/run_if_test_prerequisite'
>>> run( f'touch {prerequisite}', caller=0)
run(): Running: touch tests/run_if_test_prerequisite
>>> run_if( f'touch  {out}', out, prerequisite, caller=0)
run_if(): Running command because: Command has changed:
run_if():     @@ -1,2 +1,2 @@
run_if():      touch
run_if():     -tests/run_if_test_out;
run_if():     +tests/run_if_test_out
run_if(): Running: touch  tests/run_if_test_out
True

If we repeat, the output will be newer than the prerequisite, so the command is not run:

>>> run_if( f'touch  {out}', out, prerequisite, caller=0)
run_if(): Not running command because up to date: 'tests/run_if_test_out'

Also see OS-specific doctest examples for _run_if_test_scripting_windows() and _run_if_test_scripting_unix().

pipcl.show_sysconfig()

Shows contents of sysconfig.get_paths() and sysconfig.get_config_vars() dicts.

pipcl.show_system()

Show useful information about the system plus argv and environ.

Omits os.environ if $PIPCL_SHOW_ENV is ‘0’.

pipcl.str_to_int(s, replace=<class 'str'>)

Returns string converted to int, with configurable behaviour if the string is not convertible.

Parameters:
  • s – The input string.

  • replace

    Governs what we do if int(s) fails:
    None:

    Raise.

    str:

    Return the string <s>.

    Otherwise:

    Return <replace> itself.

pipcl.swig_get(
swig,
quick,
swig_local='pipcl-swig-git',
)

Returns <swig> or a new swig binary.

If <swig> is true and starts with ‘git:’ (not Windows), the remaining text is passed to git_get() and we clone/update/build swig, and return the built binary. We default to the main swig repository, branch master, so for example ‘git:’ will return the latest swig from branch master.

Otherwise we simply return <swig>.

Parameters:
  • swig

    If starts with ‘git:’, passed as <text> arg to git_get(), and we return the absolute path of the build binary. (Not windows.)

    If starts with ‘pip:’ we do: pip install swig and return ‘swig’. E.g. ‘pip:==4.3.1’.

    Otherwise we simply return <swig>.

  • quick – If true, we do not update/build local checkout if the binary is already present.

  • swig_local – path to use for checkout.

pipcl.swig_prepare_build(swig_env_extra)

Builds required tools for building swig (not Windows).

Adds to swig_env_extra[‘PATH’].

pipcl.sysconfig_python_flags()

Returns include paths and library directory for Python.

Uses sysconfig.*(), overridden by environment variables PIPCL_SYSCONFIG_PATH_include, PIPCL_SYSCONFIG_PATH_platinclude and PIPCL_SYSCONFIG_CONFIG_VAR_LIBDIR if set.

pipcl.venv_enter_cmd(venv_name)

Returns command that will enter venv, different on Windows vs Unix.

pipcl.verbose(level=None)

Sets verbose level if level is not None. Returns verbose level.

pipcl.version_override(name)

If PIPCL_CHANGE_VERSIONS is set and contains a match for <name>, we return the override version.

Otherwise we return None.

This should be called by a setup.py’s get_requires_for_build_wheel() for each item it returns.

pipcl.version_to_tuple(
version,
replace=<class 'str'>,
)

Returns a tuple containing each item from <version> converted to an int.

If an item does not convert to an int, behaviour depends on <replace>. :param version: A string containing items separated by ‘.’ :param replace:

Governs what we do if conversion of item to int fails:
None:

Raise.

str:

Use the string value.

Otherwise:

Use <replace> itself.

.-separated string as tuple, with each item converted to an int, or kept as a string if the conversion failed (or to <replace> if not str.

>>> version_to_tuple('1.2.3')
(1, 2, 3)
>>> version_to_tuple('1.2.3.alpha')
(1, 2, 3, 'alpha')
>>> version_to_tuple('1.2.3.alpha', replace=0)
(1, 2, 3, 0)
pipcl.wasm()

Returns true of we are running on Wasm.

pipcl.windows()

Returns true of we are running on Windows.

pipcl.zipfile_writestr(
zf,
name,
contents,
unix_permissions=384,
)

Alternative to zipfile.ZipFile.writestr(), which allows control over permissions. Our default 0o600 matches zipfile.ZipFile.writestr().

pipcl.wdev

Finds locations of Windows command-line development tools.

Classes:

WindowsCpu([name])

For Windows only.

WindowsPython([cpu, version, verbose])

Windows only.

WindowsVS(*[, year, grade, version, cpu, ...])

Windows only.

Functions:

windows_vs_multiple([year, grade, verbose])

Returns list of WindowsVS instances.

class pipcl.wdev.WindowsCpu(name=None)

For Windows only. Paths and names that depend on cpu.

Members:
.bits

32 or 64.

.name

One of: arm32 arm64 x32 x64.

.windows_subdir

Empty string, x64/, arm64 etc.

.windows_name

x86,`x64`, arm64 etc.

.windows_config

x64, Win32, arm64 etc, e.g. for use in /Build Release|x64.

.windows_suffix

64 or empty string.

ARM members are not fixed yet.

class pipcl.wdev.WindowsPython(
cpu=None,
version=None,
verbose=True,
)

Windows only. Information about installed Python with specific word size and version. Defaults to the currently-running Python.

Members:

.path:

Path of python binary.

.version:

{major}.{minor}, e.g. 3.9 or 3.11. Same as version passed to __init__() if not None, otherwise the inferred version.

.include:

Python include path.

.cpu:

A WindowsCpu instance, same as cpu passed to __init__() if not None, otherwise the inferred cpu.

We parse the output from py -0p to find all available python installations.

Parameters:
  • cpu – A WindowsCpu instance. If None, we use whatever we are running on.

  • version – Two-digit Python version as a string such as 3.8. If None we use current Python’s version.

  • verbose – If true we show diagnostics.

Methods:

description_ml([indent])

description_ml(indent='')
class pipcl.wdev.WindowsVS(
*,
year=None,
grade=None,
version=None,
cpu=None,
directory=None,
verbose=False,
)

Windows only. Finds locations of Visual Studio command-line tools. Assumes VS2019-style paths.

Members and example values:

.year:      2019
.grade:     Community
.version:   14.28.29910
.directory: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
.vcvars:    C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat
.cl:        C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\bin\Hostx64\x64\cl.exe
.link:      C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\bin\Hostx64\x64\link.exe
.csc:       C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\Roslyn\csc.exe
.msbuild:   C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe
.devenv:    C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\devenv.com
.tools:     C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\devenv.com
.toolvs:    [142, 143]

.csc is C# compiler; will be None if not found.

Parameters:
  • year – None or, for example, 2019. If None we use environment variable WDEV_VS_YEAR if set.

  • grade

    None or, for example, one of:

    • Community

    • Professional

    • Enterprise

    If None we use environment variable WDEV_VS_GRADE if set.

  • version – None or, for example: 14.28.29910. If None we use environment variable WDEV_VS_VERSION if set.

  • cpu – None or a WindowsCpu instance.

  • directory – Ignore year, grade, version and cpu and use this directory directly.

  • verbose

    .

Methods:

description_ml([indent])

Return multiline description of self.

description_ml(indent='')

Return multiline description of self.

pipcl.wdev.windows_vs_multiple(
year=None,
grade=None,
verbose=0,
)

Returns list of WindowsVS instances.