Skip to content

Commit

Permalink
implement abi3 building
Browse files Browse the repository at this point in the history
  • Loading branch information
wolfv committed Jan 15, 2025
1 parent 2662450 commit 3af62f4
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 16 deletions.
8 changes: 4 additions & 4 deletions docs/sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

Since the 0.34.0 release, `rattler-build` has a new experimental feature called
`sandbox`. With the sandbox feature enabled (via `--sandbox`), the build process
has much more restricted access to system resources on macOS and Linux.
has much more restricted access to system resources on macOS and Linux.
As the sandbox feature is experimental it is disabled by default.

In particular, with the default configuration, the build process can read the entire filesystem,
In particular, with the default configuration, the build process can read the entire filesystem,
but it cannot write outside of the build directories. The build process also cannot access the network.
In the future, we plan to enable the sandbox per default and restrict it further.
In the future, we plan to enable the sandbox per default and restrict it further.

On macOS this is achieved by using the `sandbox-exec` command, which is part of the macOS system.
On Linux the sandbox is created using Linux namespaces.
Expand Down Expand Up @@ -57,4 +57,4 @@ On Linux, by default, the sandbox configuration is as follows:

### Windows

Sandboxing the build process is not yet supported on Windows, and thus all passed sandbox flags are entirely ignored.
Sandboxing the build process is not yet supported on Windows, and thus all passed sandbox flags are entirely ignored.
3 changes: 2 additions & 1 deletion src/packaging/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ impl Output {
return Err(PackagingError::InvalidMetadata("Cannot set python_site_packages_path for a package that is not called `python`".to_string()));
}
}

Ok(IndexJson {
name: self.name().clone(),
version: self.version().clone().into(),
Expand All @@ -308,7 +309,7 @@ impl Output {
.map(|dep| dep.spec().to_string())
.dedup()
.collect(),
noarch: *recipe.build().noarch(),
noarch: recipe.build().noarch(),
track_features,
features: None,
python_site_packages_path: recipe.build().python().site_packages_path.clone(),
Expand Down
11 changes: 8 additions & 3 deletions src/recipe/parser/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,12 @@ impl Build {
}

/// Get the noarch type.
pub const fn noarch(&self) -> &NoArchType {
&self.noarch
pub fn noarch(&self) -> NoArchType {
if self.python().version_independent {
return NoArchType::python();
} else {
self.noarch.clone()
}
}

/// Python specific build configuration.
Expand Down Expand Up @@ -544,7 +548,8 @@ impl TryConvertNode<Python> for RenderedMappingNode {
entry_points,
skip_pyc_compilation,
use_python_app_entrypoint,
site_packages_path
site_packages_path,
version_independent
);
Ok(python)
}
Expand Down
6 changes: 4 additions & 2 deletions src/recipe/parser/requirements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,10 +522,12 @@ impl TryConvertNode<RunExports> for RenderedMappingNode {
/// Run exports to ignore
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct IgnoreRunExports {
/// Run exports to ignore by name of the package that is exported
#[serde(default, skip_serializing_if = "IndexSet::is_empty")]
pub(super) by_name: IndexSet<PackageName>,
pub by_name: IndexSet<PackageName>,
/// Run exports to ignore by the package that applies them
#[serde(default, skip_serializing_if = "IndexSet::is_empty")]
pub(super) from_package: IndexSet<PackageName>,
pub from_package: IndexSet<PackageName>,
}

impl IgnoreRunExports {
Expand Down
19 changes: 15 additions & 4 deletions src/variant_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,10 @@ impl VariantConfig {
// Now we need to convert the stage 1 renders to DiscoveredOutputs
let mut recipes = IndexSet::new();
for sx in stage_1 {
for ((node, recipe), variant) in sx.into_sorted_outputs()? {
let target_platform = if recipe.build().noarch().is_none() {
for ((node, mut recipe), variant) in sx.into_sorted_outputs()? {
let target_platform = if recipe.build().noarch().is_none()
|| recipe.build().python().version_independent
{
selector_config.target_platform
} else {
Platform::NoArch
Expand All @@ -454,16 +456,25 @@ impl VariantConfig {
.expect("Build string has to be resolved")
.to_string();

if recipe.build().python().version_independent {
recipe
.requirements
.ignore_run_exports
.from_package
.insert("python".parse().unwrap());
}

// TODO do not set target_platform to noarch
recipes.insert(DiscoveredOutput {
name: recipe.package().name.as_normalized().to_string(),
version: recipe.package().version.to_string(),
build_string,
noarch_type: *recipe.build().noarch(),
noarch_type: recipe.build().noarch(),
target_platform,
node,
used_vars: variant.clone(),
recipe: recipe.clone(),
hash: HashInfo::from_variant(&variant, recipe.build().noarch()),
hash: HashInfo::from_variant(&variant, &recipe.build().noarch()),
});
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/variant_render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ impl Stage1Render {
}

// fix target_platform value here
if !recipe.build().noarch().is_none() {
if !recipe.build().noarch().is_none() && !recipe.build().python().version_independent {
variant.insert("target_platform".into(), "noarch".into());
}

Expand All @@ -203,7 +203,7 @@ impl Stage1Render {
pub fn build_string_for_output(&self, idx: usize) -> Result<String, VariantError> {
let variant = self.variant_for_output(idx)?;
let recipe = &self.stage_0_render.rendered_outputs[idx];
let hash = HashInfo::from_variant(&variant, recipe.build().noarch());
let hash = HashInfo::from_variant(&variant, &recipe.build().noarch());
let inner = &self.inner[idx];

let mut selector_config = inner.selector_config.clone();
Expand Down
46 changes: 46 additions & 0 deletions test-data/recipes/abi3/recipe.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package:
name: python-abi3-package-sample
version: 0.0.1

source:
url: https://github.com/joerick/python-abi3-package-sample/archive/6f74ae7b31e58ef5f8f09b647364854122e61155.tar.gz
sha256: e81fd4d4c4f5b7bc9786d9ee990afc659e14a25ce11182b7b69f826407cc1718

build:
number: 0
python:
version_independent: true
script: ${{ PYTHON }} -m pip install . -vv

requirements:
build:
- ${{ compiler('c') }}
- ${{ stdlib('c') }}
host:
- python-abi3
- python
- pip
- setuptools
run:
- python

tests:
- python:
imports:
- spam
- script:
- export SP_DIR=$(python -c "import site; print(site.getsitepackages()[0])")
- abi3audit $SP_DIR/spam.abi3.so -s -v --assume-minimum-abi3 ${{ python_min }}
requirements:
run:
- abi3audit

about:
homepage: https://github.com/joerick/python-abi3-package-sample
summary: 'ABI3 example'
license: Apache-2.0
license_file: LICENSE

extra:
recipe-maintainers:
- isuruf
6 changes: 6 additions & 0 deletions test-data/recipes/abi3/variants.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
python_min:
- "3.8"
c_stdlib:
- macosx_deployment_target
c_stdlib_version:
- '11.0'
23 changes: 23 additions & 0 deletions test/end-to-end/test_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -1200,3 +1200,26 @@ def test_cache_select_files(rattler_build: RattlerBuild, recipes: Path, tmp_path
assert paths["paths"][0]["path_type"] == "softlink"
assert paths["paths"][1]["_path"] == "lib/libdav1d.so.7.0.0"
assert paths["paths"][1]["path_type"] == "hardlink"


def test_abi3(rattler_build: RattlerBuild, recipes: Path, tmp_path: Path):
rattler_build.build(recipes / "abi3", tmp_path)
pkg = get_extracted_package(tmp_path, "abi3")

assert (pkg / "info/paths.json").exists()
paths = json.loads((pkg / "info/paths.json").read_text())
# ensure that all paths start with `site-packages`
for p in paths["paths"]:
assert p["_path"].startswith("site-packages")

actual_paths = [p["_path"] for p in paths["paths"]]
if os.name == "nt":
assert "site-packages\\spam.dll" in actual_paths
else:
assert "site-packages/spam.abi3.so" in actual_paths

# load index.json
index = json.loads((pkg / "info/index.json").read_text())
assert index["name"] == "spam"
assert index["noarch"] == "python"
assert index["platform"] == host_subdir()

0 comments on commit 3af62f4

Please sign in to comment.