Skip to content

Commit

Permalink
Default ABI, version, arch for cross-compilation,
Browse files Browse the repository at this point in the history
allow absolute or relative toolchain file paths,
differences between paths relative to config file or project folder.
  • Loading branch information
tttapa committed Oct 24, 2022
1 parent 67a7134 commit 7cf7bb2
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 66 deletions.
16 changes: 8 additions & 8 deletions docs/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Defines the name and the directory of the module to package.
| Option | Description | Type | Default |
|--------|-------------|------|---------|
| `name` | Import name in Python (can be different from the name on PyPI, which is defined in the [project] section). | string | `/pyproject.toml/project/name` |
| `directory` | Directory containing the Python package. | path | `'.'` |
| `directory` | Directory containing the Python package.<br/>Relative to project directory. | path | `'.'` |

## sdist
Specifies the files that should be included in the source distribution for this package.
Expand All @@ -29,8 +29,8 @@ Defines how to build the project to package. If omitted, py-build-cmake will pro
| `build_type` | Build type passed to the configuration step, as -DCMAKE\_BUILD\_TYPE=&lt;?&gt;.<br/>For example: `build_type = "RelWithDebInfo"` | string | `none` |
| `config` | Configuration type passed to the build and install steps, as --config &lt;?&gt;. You can specify either a single string, or a list of strings. If a multi-config generator is used, all configurations in this list will be included in the package. | list | `build_type` |
| `generator` | CMake generator to use, passed to the configuration step, as -G &lt;?&gt;. | string | `none` |
| `source_path` | Folder containing CMakeLists.txt. | path | `'.'` |
| `build_path` | CMake build and cache folder. | path | `'.py-build-cmake_cache'` |
| `source_path` | Folder containing CMakeLists.txt.<br/>Relative to project directory. | path | `'.'` |
| `build_path` | CMake build and cache folder.<br/>Relative. | path | `'.py-build-cmake_cache'` |
| `options` | Extra options passed to the configuration step, as -D&lt;option&gt;=&lt;value&gt;.<br/>For example: `options = {"WITH_FEATURE_X" = "On"}` | dict | `{}` |
| `args` | Extra arguments passed to the configuration step.<br/>For example: `args = ["--debug-find", "-Wdev"]` | list | `[]` |
| `build_args` | Extra arguments passed to the build step.<br/>For example: `build_args = ["-j"]` | list | `[]` |
Expand Down Expand Up @@ -78,11 +78,11 @@ Causes py-build-cmake to cross-compile the project.

| Option | Description | Type | Default |
|--------|-------------|------|---------|
| `implementation` | Identifier for the Python implementation.<br/>For example: `implementation = 'cp' # CPython` | string | `required` |
| `version` | Python version, major and minor, without dots.<br/>For example: `version = '310' # 3.10` | string | `required` |
| `abi` | Python ABI.<br/>For example: `abi = 'cp310'` | string | `required` |
| `arch` | Operating system and architecture (no dots or dashes, only underscores, all lowercase).<br/>For example: `arch = 'linux_x86_64'` | string | `required` |
| `toolchain_file` | CMake toolchain file to use. | path | `required` |
| `implementation` | Identifier for the Python implementation.<br/>For example: `implementation = 'cp' # CPython` | string | `same as current interpreter` |
| `version` | Python version, major and minor, without dots.<br/>For example: `version = '310' # 3.10` | string | `same as current interpreter` |
| `abi` | Python ABI.<br/>For example: `abi = 'cp310'` | string | `same as current interpreter` |
| `arch` | Operating system and architecture (no dots or dashes, only underscores, all lowercase).<br/>For example: `arch = 'linux_x86_64'` | string | `same as current interpreter` |
| `toolchain_file` | CMake toolchain file to use.<br/>Absolute or relative to current configuration file. | filepath | `required` |
| `copy_from_native_build` | If set, this will cause a native version of the CMake project to be built and installed in a temporary directory first, and the files in this list will be copied to the final cross-compiled package. This is useful if you need binary utilities that run on the build system while cross-compiling, or for things like stubs for extension modules that cannot be generated while cross-compiling.<br/>May include the &#x27;\*&#x27; wildcard (but not &#x27;\*\*&#x27; for recursive patterns). | list | `none` |
| `sdist` | Override sdist options when cross-compiling.<br/>Inherits from: `/pyproject.toml/tool/py-build-cmake/sdist` | | `none` |
| `cmake` | Override CMake options when cross-compiling.<br/>Inherits from: `/pyproject.toml/tool/py-build-cmake/cmake` | | `none` |
Expand Down
22 changes: 13 additions & 9 deletions src/py_build_cmake/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,24 +524,28 @@ def run(self, *args, **kwargs):
pprint(kwargs)
return sp_run(*args, **kwargs)

@staticmethod
def get_cross_tags(crosscfg):
"""Get the PEP 425 tags to use when cross-compiling."""
return {
'pyver': [crosscfg['implementation'] + crosscfg['version']],
'abi': [crosscfg['abi']],
'arch': [crosscfg['arch']],
}

@staticmethod
def get_native_tags():
"""Get the PEP 425 tags for the current platform."""
from .tags import get_python_tag, get_abi_tag, get_platform_tag
return {
'pyver': [get_python_tag()],
'abi': [get_abi_tag()],
'arch': [get_platform_tag()],
}

@staticmethod
def get_cross_tags(crosscfg):
"""Get the PEP 425 tags to use when cross-compiling."""
tags = _BuildBackend.get_native_tags()
if 'implementation' in crosscfg and 'version' in crosscfg:
tags['pyver'] = [crosscfg['implementation'] + crosscfg['version']]
if 'abi' in crosscfg:
tags['abi'] = [crosscfg['abi']]
if 'arch' in crosscfg:
tags['arch'] = [crosscfg['arch']]
return tags

@staticmethod
def get_build_config_name(cross_cfg):
"""Get a string representing the Python version, ABI and architecture,
Expand Down
66 changes: 52 additions & 14 deletions src/py_build_cmake/config_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,16 @@ def get_name(self):

class NoDefaultValue(DefaultValue):

def __init__(self, name = 'none'):
self.name = name

def get_default(self, rootopts: 'ConfigOption', opt: 'ConfigOption',
cfg: ConfigNode, cfgpath: ConfPath,
optpath: ConfPath) -> Optional[DefaultValueWrapper]:
return None

def get_name(self):
return 'none'
return self.name


class MissingDefaultError(ConfigError):
Expand Down Expand Up @@ -488,6 +491,16 @@ def verify(self, rootopts: 'ConfigOption', cfg: ConfigNode,
f'{str}, not {type(cfg[cfgpath].value)}')


class RelativeToCurrentConfig:
description = 'current configuration file'


@dataclass
class RelativeToProject:
project_path: Path
description: str = 'project directory'


class PathConfigOption(StrConfigOption):

def __init__(self,
Expand All @@ -497,32 +510,57 @@ def __init__(self,
default: DefaultValue = NoDefaultValue(),
must_exist: bool = True,
expected_contents: List[str] = [],
base_path: Optional[Path] = None):
base_path: Optional[Union[RelativeToProject,
RelativeToCurrentConfig]] = None,
allow_abs: bool = False,
is_folder: bool = True):
super().__init__(name, description, example, default)
self.must_exist = must_exist
self.must_exist = must_exist or bool(expected_contents)
self.expected_contents = expected_contents
self.base_path = base_path
self.allow_abs = allow_abs
self.is_folder = is_folder

def get_typename(self):
return 'path'
return 'path' if self.is_folder else 'filepath'

def check_path(self, cfg: ConfigNode, cfgpath):
path = cfg[cfgpath].value = os.path.normpath(cfg[cfgpath].value)
path = os.path.normpath(cfg[cfgpath].value)
# Absolute or relative path?
if os.path.isabs(path):
raise ConfigError(f'{pth2str(cfgpath)} must be a relative path')
if self.base_path is not None:
abspath = self.base_path / path
if self.must_exist and not os.path.exists(abspath):
raise ConfigError(f'{pth2str(cfgpath)}: {str(abspath)} '
f'does not exist')
# Absolute path
if not self.allow_abs:
raise ConfigError(f'{pth2str(cfgpath)}: "{str(path)}" '
f'must be a relative path')
else:
# Relative path
if isinstance(self.base_path, RelativeToCurrentConfig):
path = os.path.join(os.path.dirname(cfgpath[0]), path)
elif isinstance(self.base_path, RelativeToProject):
path = os.path.join(self.base_path.project_path, path)
else:
assert self.base_path is None
# Does the path exist?
if self.must_exist:
assert os.path.isabs(path)
if not os.path.exists(path):
raise ConfigError(f'{pth2str(cfgpath)}: "{str(path)}" '
f'does not exist')
if self.is_folder != os.path.isdir(path):
type_ = 'directory' if self.is_folder else 'file'
raise ConfigError(f'{pth2str(cfgpath)}: "{str(path)}" '
f'should be a {type_}')
# Are any of the required contents missing?
missing = [
sub for sub in self.expected_contents
if not os.path.exists(os.path.join(abspath, sub))
if not os.path.exists(os.path.join(path, sub))
]
if missing:
missingstr = '", "'.join(missing)
raise ConfigError(f'{pth2str(cfgpath)} does not contain '
f'required files or folders "{missingstr}"')
raise ConfigError(f'{pth2str(cfgpath)}: "{str(path)}" '
f'does not contain the following '
f'required files or folders: "{missingstr}"')
cfg[cfgpath].value = os.path.normpath(path)

def verify(self, rootopts: 'ConfigOption', cfg: ConfigNode,
cfgpath: ConfPath):
Expand Down
16 changes: 14 additions & 2 deletions src/py_build_cmake/help/__main__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import html
import itertools
from pathlib import Path
import shutil
import sys
import textwrap

from py_build_cmake.config_options import ConfigOption, NoDefaultValue, RefDefaultValue, RequiredValue, pth, pth2str
from py_build_cmake.config_options import ConfigOption, NoDefaultValue, PathConfigOption, RefDefaultValue, RequiredValue, pth, pth2str
from py_build_cmake.pyproject_options import get_options


Expand Down Expand Up @@ -45,6 +46,8 @@ def help_print_md(pbc_opts: ConfigOption):

def _get_full_description(vv: ConfigOption):
descr = _md_escape(vv.description)
if isinstance(vv, PathConfigOption):
descr += '<br/>' + _describe_path_option(vv).capitalize() + '.'
if vv.inherit_from:
descr += '<br/>Inherits from: `/' + pth2str(vv.inherit_from) + '`'
if vv.example:
Expand Down Expand Up @@ -78,6 +81,8 @@ def recursive_help_print(opt: ConfigOption, level=0):
typename = v.get_typename()
if typename is not None:
headerfields += [typename]
if isinstance(v, PathConfigOption):
headerfields += [_describe_path_option(v)]
is_required = isinstance(v.default, RequiredValue)
if is_required:
headerfields += ['required']
Expand All @@ -94,6 +99,13 @@ def recursive_help_print(opt: ConfigOption, level=0):
print(textwrap.indent('Default: ' + default, indent + ' '))


def _describe_path_option(v: PathConfigOption):
t = 'absolute or relative' if v.allow_abs else 'relative'
if v.base_path is not None:
t += ' to ' + v.base_path.description
return t


def _print_usage():
print(
textwrap.dedent("""\
Expand All @@ -112,7 +124,7 @@ def _print_usage():


def main():
opts = get_options()
opts = get_options(Path('/'))
help_pth = pth('pyproject.toml/tool/py-build-cmake')
help_opt = {'-h', '-?', '--help', 'h', 'help', '?'}
if len(sys.argv) == 2 and sys.argv[1] == 'md':
Expand Down
23 changes: 14 additions & 9 deletions src/py_build_cmake/pyproject_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def get_cross_path():
return pth('pyproject.toml/tool/py-build-cmake/cross')


def get_options(config_path: Optional[Path] = None):
def get_options(project_path: Path, *, test: bool = False):
root = ConfigOption("root")
pyproject = root.insert(UncheckedConfigOption("pyproject.toml"))
project = pyproject.insert(UncheckedConfigOption('project'))
Expand Down Expand Up @@ -41,7 +41,8 @@ def get_options(config_path: Optional[Path] = None):
PathConfigOption('directory',
"Directory containing the Python package.",
default=DefaultValueValue("."),
base_path=config_path),
base_path=RelativeToProject(project_path),
must_exist=not test),
])

# [tool.py-build-cmake.sdist]
Expand Down Expand Up @@ -101,8 +102,9 @@ def get_options(config_path: Optional[Path] = None):
PathConfigOption('source_path',
"Folder containing CMakeLists.txt.",
default=DefaultValueValue("."),
expected_contents=["CMakeLists.txt"],
base_path=config_path),
expected_contents=[] if test else ["CMakeLists.txt"],
base_path=RelativeToProject(project_path),
must_exist=not test),
PathConfigOption('build_path',
"CMake build and cache folder.",
default=DefaultValueValue('.py-build-cmake_cache'),
Expand Down Expand Up @@ -197,24 +199,27 @@ def get_options(config_path: Optional[Path] = None):
StrConfigOption('implementation',
"Identifier for the Python implementation.",
"implementation = 'cp' # CPython",
default=RequiredValue()),
default=NoDefaultValue('same as current interpreter')),
StrConfigOption('version',
"Python version, major and minor, without dots.",
"version = '310' # 3.10",
default=RequiredValue()),
default=NoDefaultValue('same as current interpreter')),
StrConfigOption('abi',
"Python ABI.",
"abi = 'cp310'",
default=RequiredValue()),
default=NoDefaultValue('same as current interpreter')),
StrConfigOption('arch',
"Operating system and architecture (no dots or "
"dashes, only underscores, all lowercase).",
"arch = 'linux_x86_64'",
default=RequiredValue()),
default=NoDefaultValue('same as current interpreter')),
PathConfigOption('toolchain_file',
"CMake toolchain file to use.",
default=RequiredValue(),
base_path=config_path),
base_path=RelativeToCurrentConfig(),
must_exist=not test,
allow_abs=True,
is_folder=False),
ListOfStrConfigOption('copy_from_native_build',
"If set, this will cause a native version of the "
"CMake project to be built and installed in a "
Expand Down
Loading

0 comments on commit 7cf7bb2

Please sign in to comment.