Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,13 @@ jobs:
with:
targets: ${{ matrix.target }}

- name: Set up Python
- name: Set up Python (Unix)
if: ${{ !contains(matrix.target, 'windows') }}
uses: actions/setup-python@v6
with:
python-version: '3.13'

- name: Set up Python (Windows)
if: contains(matrix.target, 'windows')
uses: actions/setup-python@v6
with:
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,13 @@ jobs:
with:
targets: ${{ matrix.target }}

- name: Set up Python
- name: Set up Python (Unix)
if: ${{ !contains(matrix.target, 'windows') }}
uses: actions/setup-python@v6
with:
python-version: '3.13'

- name: Set up Python (Windows)
if: contains(matrix.target, 'windows')
uses: actions/setup-python@v6
with:
Expand Down Expand Up @@ -241,6 +247,7 @@ jobs:
- `runtime/` directory with the platform-specific runtime library
- `decode-runtime/` directory with the platform-specific decode runtime library
- `decoders/` directory with bundled DSView decoder scripts
- `python/` directory with the bundled Python runtime used by protocol decoding
- `resources/` directory with DSLogic Plus firmware and bitstreams

### Installation
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ The native integration is intentionally isolated behind Rust layers to keep unsa

### Packaging

Release bundles are created using `tools/package-bundle.py` and validated with `tools/validate-bundle.py`. CI uses these Python helpers to ensure consistent bundle structure across all platforms, including the bundled capture runtime, decode runtime, firmware resources, and decoder scripts, without relying on unstable Cargo script support.
Release bundles are created using `tools/package-bundle.py` and validated with `tools/validate-bundle.py`. CI uses these Python helpers to ensure consistent bundle structure across all platforms, including the bundled capture runtime, decode runtime, Python runtime, firmware resources, and decoder scripts, without relying on unstable Cargo script support.

## License

Expand Down
5 changes: 1 addition & 4 deletions crates/dsview-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1332,10 +1332,7 @@ impl DecodeDiscoveryPaths {
developer_decoder_dir()
};

let python_home = if cfg!(windows)
&& runtime_library == bundled_runtime
&& bundled_python_home.is_dir()
{
let python_home = if runtime_library == bundled_runtime && bundled_python_home.is_dir() {
Some(bundled_python_home)
} else {
None
Expand Down
41 changes: 39 additions & 2 deletions crates/dsview-core/tests/bundle_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};

use dsview_core::RuntimeDiscoveryPaths;
use dsview_sys::{runtime_library_name, source_runtime_library_path};
use dsview_core::{DecodeDiscoveryPaths, RuntimeDiscoveryPaths};
use dsview_sys::{
decode_runtime_library_name, runtime_library_name, source_runtime_library_path,
};

fn temp_dir(name: &str) -> PathBuf {
let unique = SystemTime::now()
Expand All @@ -22,6 +24,11 @@ fn write_valid_resources(dir: &std::path::Path) {
fs::write(dir.join("DSLogicPlus-pgl12.bin"), b"bin").unwrap();
}

fn write_valid_decoder_dir(dir: &std::path::Path) {
fs::create_dir_all(dir).unwrap();
fs::write(dir.join("spi.py"), b"# decoder placeholder").unwrap();
}

#[test]
fn bundle_defaults_resolve_from_executable_layout() {
let exe_dir = temp_dir("bundle-defaults");
Expand Down Expand Up @@ -177,6 +184,36 @@ fn bundle_layout_matches_packaging_contract() {
assert!(resource_dir.join("DSLogicPlus-pgl12.bin").is_file());
}

#[test]
fn decode_bundle_discovery_uses_bundled_python_home_on_all_platforms() {
let exe_dir = temp_dir("decode-python-home");
let decode_runtime_dir = exe_dir.join("decode-runtime");
let decoder_dir = exe_dir.join("decoders");
let python_home = exe_dir.join("python");
fs::create_dir_all(&decode_runtime_dir).unwrap();
fs::create_dir_all(&python_home).unwrap();
fs::write(
decode_runtime_dir.join(decode_runtime_library_name()),
b"decode runtime",
)
.unwrap();
write_valid_decoder_dir(&decoder_dir);

let paths = DecodeDiscoveryPaths::from_executable_dir(
&exe_dir,
None::<&std::path::Path>,
None::<&std::path::Path>,
)
.expect("decode bundle-relative discovery should succeed");

assert_eq!(
paths.runtime_library,
decode_runtime_dir.join(decode_runtime_library_name())
);
assert_eq!(paths.decoder_dir, decoder_dir);
assert_eq!(paths.python_home, Some(python_home));
}

#[test]
fn runtime_library_name_helper_is_consistent() {
// Verify the helper returns the same value across calls
Expand Down
57 changes: 54 additions & 3 deletions crates/dsview-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ struct SourceRuntimeArtifacts {
decode_library_path: PathBuf,
}

struct PythonDiscovery {
executable: PathBuf,
root: PathBuf,
}

fn main() {
let target = TargetInfo::from_cargo_env();
let manifest_dir =
Expand Down Expand Up @@ -334,23 +339,24 @@ fn build_source_runtimes(
}
}

if !command_available("python3") {
return Err("python3 is not available".to_string());
}
let python =
discover_python().ok_or_else(|| "python3 or python is not available".to_string())?;

let capture_library_path = build_source_runtime_variant(
repo_root,
native_root,
target,
"source-runtime-build",
"capture",
None,
)?;
let decode_library_path = build_source_runtime_variant(
repo_root,
native_root,
target,
"source-decode-runtime-build",
"decode",
Some(&python),
)?;

Ok(SourceRuntimeArtifacts {
Expand All @@ -365,6 +371,7 @@ fn build_source_runtime_variant(
target: &TargetInfo,
build_dir_name: &str,
runtime_kind: &str,
python: Option<&PythonDiscovery>,
) -> Result<PathBuf, String> {
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is set by Cargo"));
let build_dir = out_dir.join(build_dir_name);
Expand Down Expand Up @@ -392,6 +399,11 @@ fn build_source_runtime_variant(
configure
.arg("-DDSVIEW_BUILD_CAPTURE_RUNTIME=OFF")
.arg("-DDSVIEW_BUILD_DECODE_RUNTIME=ON");
if let Some(python) = python {
configure
.arg(format!("-DPython3_EXECUTABLE={}", python.executable.display()))
.arg(format!("-DPython3_ROOT_DIR={}", python.root.display()));
}
}
_ => return Err(format!("unknown source runtime kind `{runtime_kind}`")),
}
Expand Down Expand Up @@ -627,6 +639,45 @@ fn command_available(command: &str) -> bool {
.unwrap_or(false)
}

fn discover_python() -> Option<PythonDiscovery> {
let mut candidates = Vec::new();
if let Ok(explicit) = env::var("PYTHON") {
candidates.push(explicit);
}
candidates.extend(["python3".to_string(), "python".to_string()]);

for candidate in candidates {
if !command_available(&candidate) {
continue;
}

let Ok(output) = Command::new(&candidate)
.arg("-c")
.arg("import sys; print(sys.executable); print(sys.base_exec_prefix)")
.output()
else {
continue;
};
if !output.status.success() {
continue;
}

let Ok(stdout) = String::from_utf8(output.stdout) else {
continue;
};
let mut lines = stdout.lines();
let Some(executable) = lines.next().map(PathBuf::from) else {
continue;
};
let Some(root) = lines.next().map(PathBuf::from) else {
continue;
};
return Some(PythonDiscovery { executable, root });
}

None
}

fn pkg_config_has(package: &str) -> bool {
let Some(pkg_config) = pkg_config_command() else {
return false;
Expand Down
10 changes: 10 additions & 0 deletions crates/dsview-sys/native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,16 @@ if(DSVIEW_BUILD_DECODE_RUNTIME)
set_target_properties(dsview_decode_runtime PROPERTIES
LINK_FLAGS "/DEF:${DSVIEW_NATIVE_WINDOWS_ROOT}/dsview_decode_runtime.def"
)
elseif(APPLE)
set_target_properties(dsview_decode_runtime PROPERTIES
BUILD_RPATH "@loader_path/../python;@loader_path/../python/lib"
INSTALL_RPATH "@loader_path/../python;@loader_path/../python/lib"
)
else()
set_target_properties(dsview_decode_runtime PROPERTIES
BUILD_RPATH "$ORIGIN/../python/lib"
INSTALL_RPATH "$ORIGIN/../python/lib"
)
endif()

set_target_properties(dsview_decode_runtime PROPERTIES OUTPUT_NAME dsview_decode_runtime)
Expand Down
Loading
Loading