cargo: Add API version into dependency name

This commit is contained in:
Xavier Claessens 2023-10-11 16:41:19 -04:00 committed by Xavier Claessens
parent 3f73aaed2d
commit 4d3fb88753
14 changed files with 82 additions and 22 deletions

View File

@ -316,11 +316,24 @@ foo-bar-1.0 = foo_bar_dep
``` ```
### Cargo wraps ### Cargo wraps
Cargo subprojects automatically override the `<package_name>-rs` dependency name. Cargo subprojects automatically override the `<package_name>-<version>-rs` dependency
`package_name` is defined in `[package] name = ...` section of the `Cargo.toml` name:
and `-rs` suffix is added. That means the `.wrap` file should have - `package_name` is defined in `[package] name = ...` section of the `Cargo.toml`.
`dependency_names = foo-rs` in their `[provide]` section when `Cargo.toml` has - `version` is the API version deduced from `[package] version = ...` as follow:
package name `foo`. * `x.y.z` -> 'x'
* `0.x.y` -> '0.x'
* `0.0.x` -> '0'
It allows to make different dependencies for uncompatible versions of the same
crate.
- `-rs` suffix is added to distinguish from regular system dependencies, for
example `gstreamer-1.0` is a system pkg-config dependency and `gstreamer-0.22-rs`
is a Cargo dependency.
That means the `.wrap` file should have `dependency_names = foo-1-rs` in their
`[provide]` section when `Cargo.toml` has package name `foo` and version `1.2`.
Note that the version component was added in Meson 1.4, previous versions were
using `<package_name>-rs` format.
Cargo subprojects require a toml parser. Python >= 3.11 have one built-in, older Cargo subprojects require a toml parser. Python >= 3.11 have one built-in, older
Python versions require either the external `tomli` module or `toml2json` program. Python versions require either the external `tomli` module or `toml2json` program.
@ -332,26 +345,26 @@ file like that:
... ...
method = cargo method = cargo
[provide] [provide]
dependency_names = foo-bar-rs dependency_names = foo-bar-0.1-rs
``` ```
Cargo features are exposed as Meson boolean options, with the `feature-` prefix. Cargo features are exposed as Meson boolean options, with the `feature-` prefix.
For example the `default` feature is named `feature-default` and can be set from For example the `default` feature is named `feature-default` and can be set from
the command line with `-Dfoo-rs:feature-default=false`. When a cargo subproject the command line with `-Dfoo-1-rs:feature-default=false`. When a cargo subproject
depends on another cargo subproject, it will automatically enable features it depends on another cargo subproject, it will automatically enable features it
needs using the `dependency('foo-rs', default_options: ...)` mechanism. However, needs using the `dependency('foo-1-rs', default_options: ...)` mechanism. However,
unlike Cargo, the set of enabled features is not managed globally. Let's assume unlike Cargo, the set of enabled features is not managed globally. Let's assume
the main project depends on `foo-rs` and `bar-rs`, and they both depend on the main project depends on `foo-1-rs` and `bar-1-rs`, and they both depend on
`common-rs`. The main project will first look up `foo-rs` which itself will `common-1-rs`. The main project will first look up `foo-1-rs` which itself will
configure `common-rs` with a set of features. Later, when `bar-rs` does a lookup configure `common-rs` with a set of features. Later, when `bar-1-rs` does a lookup
for `common-rs` it has already been configured and the set of features cannot be for `common-1-rs` it has already been configured and the set of features cannot be
changed. If `bar-rs` wants extra features from `common-rs`, Meson will error out. changed. If `bar-1-rs` wants extra features from `common-1-rs`, Meson will error out.
It is currently the responsability of the main project to resolve those It is currently the responsability of the main project to resolve those
issues by enabling extra features on each subproject: issues by enabling extra features on each subproject:
```meson ```meson
project(..., project(...,
default_options: { default_options: {
'common-rs:feature-something': true, 'common-1-rs:feature-something': true,
}, },
) )
``` ```

View File

@ -0,0 +1,19 @@
## Cargo dependencies names now include the API version
Cargo dependencies names are now in the format `<package_name>-<version>-rs`:
- `package_name` is defined in `[package] name = ...` section of the `Cargo.toml`.
- `version` is the API version deduced from `[package] version = ...` as follow:
* `x.y.z` -> 'x'
* `0.x.y` -> '0.x'
* `0.0.x` -> '0'
It allows to make different dependencies for uncompatible versions of the same
crate.
- `-rs` suffix is added to distinguish from regular system dependencies, for
example `gstreamer-1.0` is a system pkg-config dependency and `gstreamer-0.22-rs`
is a Cargo dependency.
That means the `.wrap` file should have `dependency_names = foo-1-rs` in their
`[provide]` section when `Cargo.toml` has package name `foo` and version `1.2`.
This is a breaking change (Cargo subprojects are still experimental), previous
versions were using `<package_name>-rs` format.

View File

@ -162,9 +162,23 @@ class Dependency:
package: str = '' package: str = ''
default_features: bool = True default_features: bool = True
features: T.List[str] = dataclasses.field(default_factory=list) features: T.List[str] = dataclasses.field(default_factory=list)
api: str = dataclasses.field(init=False)
def __post_init__(self, name: str) -> None: def __post_init__(self, name: str) -> None:
self.package = self.package or name self.package = self.package or name
# Extract wanted API version from version constraints.
api = set()
for v in self.version:
if v.startswith(('>=', '==')):
api.add(_version_to_api(v[2:].strip()))
elif v.startswith('='):
api.add(_version_to_api(v[1:].strip()))
if not api:
self.api = '0'
elif len(api) == 1:
self.api = api.pop()
else:
raise MesonException(f'Cannot determine minimum API version from {self.version}.')
@classmethod @classmethod
def from_raw(cls, name: str, raw: manifest.DependencyV) -> Dependency: def from_raw(cls, name: str, raw: manifest.DependencyV) -> Dependency:
@ -351,8 +365,21 @@ def _load_manifests(subdir: str) -> T.Dict[str, Manifest]:
return manifests return manifests
def _dependency_name(package_name: str) -> str: def _version_to_api(version: str) -> str:
return package_name if package_name.endswith('-rs') else f'{package_name}-rs' # x.y.z -> x
# 0.x.y -> 0.x
# 0.0.x -> 0
vers = version.split('.')
if int(vers[0]) != 0:
return vers[0]
elif len(vers) >= 2 and int(vers[1]) != 0:
return f'0.{vers[1]}'
return '0'
def _dependency_name(package_name: str, api: str) -> str:
basename = package_name[:-3] if package_name.endswith('-rs') else package_name
return f'{basename}-{api}-rs'
def _dependency_varname(package_name: str) -> str: def _dependency_varname(package_name: str) -> str:
@ -517,7 +544,7 @@ def _create_dependencies(cargo: Manifest, build: builder.Builder) -> T.List[mpar
build.assign( build.assign(
build.function( build.function(
'dependency', 'dependency',
[build.string(_dependency_name(dep.package))], [build.string(_dependency_name(dep.package, dep.api))],
kw, kw,
), ),
_dependency_varname(dep.package), _dependency_varname(dep.package),
@ -559,7 +586,7 @@ def _create_dependencies(cargo: Manifest, build: builder.Builder) -> T.List[mpar
build.if_(build.not_in(build.identifier('f'), build.identifier('actual_features')), build.block([ build.if_(build.not_in(build.identifier('f'), build.identifier('actual_features')), build.block([
build.function('error', [ build.function('error', [
build.string('Dependency'), build.string('Dependency'),
build.string(_dependency_name(dep.package)), build.string(_dependency_name(dep.package, dep.api)),
build.string('previously configured with features'), build.string('previously configured with features'),
build.identifier('actual_features'), build.identifier('actual_features'),
build.string('but need'), build.string('but need'),
@ -666,7 +693,7 @@ def _create_lib(cargo: Manifest, build: builder.Builder, crate_type: manifest.CR
'override_dependency', 'override_dependency',
build.identifier('meson'), build.identifier('meson'),
[ [
build.string(_dependency_name(cargo.package.name)), build.string(_dependency_name(cargo.package.name, _version_to_api(cargo.package.version))),
build.identifier('dep'), build.identifier('dep'),
], ],
), ),
@ -674,7 +701,8 @@ def _create_lib(cargo: Manifest, build: builder.Builder, crate_type: manifest.CR
def interpret(subp_name: str, subdir: str, env: Environment) -> T.Tuple[mparser.CodeBlockNode, KeyedOptionDictType]: def interpret(subp_name: str, subdir: str, env: Environment) -> T.Tuple[mparser.CodeBlockNode, KeyedOptionDictType]:
package_name = subp_name[:-3] if subp_name.endswith('-rs') else subp_name # subp_name should be in the form "foo-0.1-rs"
package_name = subp_name.rsplit('-', 2)[0]
manifests = _load_manifests(os.path.join(env.source_dir, subdir)) manifests = _load_manifests(os.path.join(env.source_dir, subdir))
cargo = manifests.get(package_name) cargo = manifests.get(package_name)
if not cargo: if not cargo:

View File

@ -1,6 +1,6 @@
project('cargo subproject', 'c') project('cargo subproject', 'c')
foo_dep = dependency('foo-rs') foo_dep = dependency('foo-0-rs')
exe = executable('app', 'main.c', exe = executable('app', 'main.c',
dependencies: foo_dep, dependencies: foo_dep,
) )

View File

@ -7,4 +7,4 @@ d = declare_dependency(link_with: l,
variables: { variables: {
'features': 'default', 'features': 'default',
}) })
meson.override_dependency('extra-dep-rs', d) meson.override_dependency('extra-dep-1-rs', d)