Skip to content

Commit 1c934cf

Browse files
committed
Build main opsqueue binary in Nix using Crane, caching deps
- Make usage of `crane` an implementation detail; - When building with Nix, people get to customize what `rustToolchain` is used, by either setting that in the overlay or directly overriding it when using `callPackage`. - Compile opsqueue_python also with Crane (+ Maturin). - Document how the Nix Crane<->Maturin setup works - Bump Rust version in rust-toolchain.toml from 1.85 to 1.92 This is now also the version used when building with Nix! - Boyscout rule: Fix warning about unused Cargo.toml lib.include key - Boyscout rule: Disable integration test timeouts, to test speed of the Nix-based integration suite run on CI
1 parent 92dc409 commit 1c934cf

16 files changed

Lines changed: 158 additions & 145 deletions

File tree

.pre-commit-config.yaml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,6 @@ repos:
2525
language: system
2626
types: [python]
2727
require_serial: true
28-
- id: biome
29-
name: Biome formatting
30-
entry: biome format --write --files-ignore-unknown=true --no-errors-on-unmatched
31-
language: system
32-
types: [json]
33-
require_serial: true
3428
- id: rustfmt
3529
name: rustfmt
3630
entry: cargo fmt --

biome.json

Lines changed: 0 additions & 17 deletions
This file was deleted.

justfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ test-integration *TEST_ARGS: build-bin build-python
4747
cd libs/opsqueue_python
4848
source "./.setup_local_venv.sh"
4949

50-
timeout 30 pytest --color=yes {{TEST_ARGS}}
50+
pytest --color=yes {{TEST_ARGS}}
5151

5252
# Python integration test suite, using artefacts built through Nix. Args are forwarded to pytest
5353
[group('nix')]
54-
nix-test-integration *TEST_ARGS:
54+
nix-test-integration *TEST_ARGS: nix-build
5555
#!/usr/bin/env bash
5656
set -euxo pipefail
5757
nix_build_python_library_dir=$(just nix-build-python)
@@ -61,7 +61,7 @@ nix-test-integration *TEST_ARGS:
6161
export OPSQUEUE_VIA_NIX=true
6262
export RUST_LOG="opsqueue=debug"
6363

64-
timeout 30 pytest --color=yes {{TEST_ARGS}}
64+
pytest --color=yes {{TEST_ARGS}}
6565

6666
# Run all linters, fast and slow
6767
[group('lint')]

libs/opsqueue_python/examples/tracing/tracing_consumer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# ConsoleSpanExporter,
99
)
1010
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
11-
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
11+
from opentelemetry.sdk.resources import SERVICE_NAME, Resource # type: ignore[attr-defined]
1212

1313
logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.INFO)
1414

libs/opsqueue_python/examples/tracing/tracing_producer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# ConsoleSpanExporter,
1414
)
1515
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
16-
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
16+
from opentelemetry.sdk.resources import SERVICE_NAME, Resource # type: ignore[attr-defined]
1717

1818
from opsqueue.producer import ProducerClient
1919

@@ -75,7 +75,7 @@ def added_baggage(
7575
yield
7676
finally:
7777
for attached_token in attached_context_tokens:
78-
opentelemetry.context.detach(attached_token)
78+
opentelemetry.context.detach(attached_token) # type: ignore[arg-type]
7979

8080

8181
if __name__ == "__main__":

libs/opsqueue_python/opsqueue_python.nix

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,92 @@
11
{
2+
# Builtin
3+
pkgs,
24
lib,
5+
6+
# Rust version. (Override this with an overlay if you like)
7+
rustToolchain,
8+
9+
# Native build dependencies:
10+
python,
11+
maturin,
312
buildPythonPackage,
4-
rustPlatform,
513
perl,
614
git,
7-
# Python packages:
15+
16+
# Python package dependencies:
817
cbor2,
918
opentelemetry-api,
1019
opentelemetry-exporter-otlp,
1120
opentelemetry-sdk,
1221
}:
1322
let
14-
root = ../../.;
15-
util = import (root + /nix/util.nix) { inherit lib; };
16-
in
17-
buildPythonPackage rec {
18-
pname = "opsqueue";
19-
version = "0.1.0";
20-
pyproject = true;
23+
sources = import ../../nix/sources.nix;
24+
crane = import sources.crane { pkgs = pkgs; };
25+
craneLib = crane.overrideToolchain (pkgs: rustToolchain);
26+
extraFileFilter = path: _type: builtins.match ".*(db|sql|py|md)$" path != null;
27+
fileFilter = path: type: (extraFileFilter path type) || (craneLib.filterCargoSources path type);
2128

22-
src = util.fileFilter {
23-
name = "opsqueue_python";
24-
src = root;
29+
src = lib.cleanSourceWith {
30+
src = ../../.;
31+
name = "opsqueue";
32+
filter = fileFilter;
33+
};
2534

26-
# We're copying slightly too much to the Nix store here,
27-
# but using the more granular file filter was very error-prone.
28-
# This is one thing that could be improved a little in the future.
29-
srcGlobalWhitelist = [
30-
".py"
31-
".pyi"
32-
"py.typed"
33-
".rs"
34-
".toml"
35-
".lock"
36-
".db"
37-
".md"
38-
];
35+
crateName = craneLib.crateNameFromCargoToml { cargoToml = ./Cargo.toml; };
36+
pname = crateName.pname;
37+
version = crateName.version;
38+
commonArgs = {
39+
inherit src version pname;
40+
strictDeps = true;
41+
nativeBuildInputs = [ python ];
42+
cargoExtraArgs = "--package opsqueue_python";
3943
};
44+
cargoArtifacts = craneLib.buildDepsOnly (commonArgs // { pname = pname; });
4045

41-
cargoDeps = rustPlatform.importCargoLock { lockFile = ../../Cargo.lock; };
46+
wheelTail = "py3-abi3-linux_x86_64";
47+
wheelName = "opsqueue-${version}-${wheelTail}.whl";
4248

43-
env = {
44-
DATABASE_URL = "sqlite://./opsqueue/opsqueue_example_database_schema.db";
45-
};
49+
crateWheel =
50+
(craneLib.buildPackage (commonArgs // { inherit cargoArtifacts; })).overrideAttrs
51+
(old: {
52+
nativeBuildInputs = old.nativeBuildInputs ++ [ maturin ];
4653

47-
pythonImportsCheck = [ pname ];
54+
# We intentionally _override_ rather than extend the buildPhase
55+
# as Maturin itself calls `cargo build`, no need to call it twice.
56+
buildPhase = ''
57+
cargo --version
58+
maturin build --release --offline --target-dir ./target --manifest-path "./libs/opsqueue_python/Cargo.toml"
59+
'';
4860

49-
maturinBuildFlags = [
50-
"--manifest-path"
51-
"./libs/opsqueue_python/Cargo.toml"
52-
];
61+
# We build a single wheel
62+
# but by convention its name is based on the precise combination of
63+
# Python version + OS version + architecture + ...
64+
#
65+
# The Nix hash already covers us for uniqueness and compatibility.
66+
# So this 'trick' copies it to a predictably named file.
67+
#
68+
# Just like `buildPhase`, we override rather than extend
69+
# because we are only interested in the wheel output of Maturin as a whole.
70+
# (which is an archive inside of it containing the `.so` cargo built)
71+
installPhase = ''
72+
mkdir -p $out
73+
for wheel in ./target/wheels/*.whl ; do
74+
cp "$wheel" $out/${wheelName}
75+
done
76+
'';
5377

54-
nativeBuildInputs = with rustPlatform; [
55-
perl
56-
git
57-
cargoSetupHook
58-
maturinBuildHook
59-
];
78+
# There are no Rust unit tests in the Python FFI library currently,
79+
# so we can skip rebuilding opsqueue_python for tests.
80+
doCheck = false;
81+
});
82+
in
83+
buildPythonPackage {
84+
pname = pname;
85+
format = "wheel";
86+
version = crateName.version;
87+
src = "${crateWheel}/${wheelName}";
88+
doCheck = false;
89+
pythonImportsCheck = [ "opsqueue" ];
6090

6191
propagatedBuildInputs = [
6292
cbor2

libs/opsqueue_python/pyproject.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ test = [
5252
"pytest==8.3.3",
5353
"pytest-random-order==1.1.1",
5454
"pytest-parallel==0.1.1",
55-
"pytest-timeout==2.4.0",
55+
# "pytest-timeout==2.4.0",
5656
"py==1.11.0", # Needs to be manually specified because of this issue: https://github.com/kevlened/pytest-parallel/issues/118
5757
]
5858

5959
[tool.pytest.ini_options]
6060
# We ensure tests never rely on global state,
6161
# by running them in a random order, and in parallel:
62-
addopts = "--random-order --workers=auto"
63-
# Individual tests should be very fast. They should never take multiple seconds
64-
# If after 20sec (accomodating for a toaster-like PC) there is no progress,
65-
# assume a deadlock
66-
timeout=20
62+
addopts = "--random-order --workers=4"
63+
# # Individual tests should be very fast. They should never take multiple seconds
64+
# # If after 20sec (accomodating for a toaster-like PC) there is no progress,
65+
# # assume a deadlock
66+
# timeout=20

libs/opsqueue_python/src/common.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -428,11 +428,8 @@ pub async fn check_signals_in_background() -> FatalPythonException {
428428
if let Err(err) = py.check_signals() {
429429
// A signal was triggered
430430
Some(err)
431-
} else if let Some(err) = PyErr::take(py) {
432-
// A non-signal Python exception was thrown
433-
return Some(err);
434431
} else {
435-
return None;
432+
PyErr::take(py)
436433
}
437434
});
438435
if let Some(res) = res {

libs/opsqueue_python/src/consumer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ impl ConsumerClient {
418418
}
419419

420420
done_count = done_count.saturating_add(1);
421-
if done_count % 50 == 0 {
421+
if done_count.is_multiple_of(50) {
422422
tracing::info!("Processed {} chunks", done_count);
423423
}
424424
}

nix/nixpkgs-pinned.nix

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ let
1010
used_overlays = [
1111
(import sources.rust-overlay)
1212
(import ./overlay.nix)
13-
] ++ overlays;
13+
]
14+
++ overlays;
1415

1516
nixpkgsArgs = args // {
1617
overlays = used_overlays;

0 commit comments

Comments
 (0)