Compare commits

...

7 Commits

Author SHA1 Message Date
bahdotsh
7ac18f3715 Release 0.7.2
wrkflw-runtime@0.7.2
wrkflw-utils@0.7.2

Generated by cargo-workspaces
2025-08-28 08:13:02 +05:30
Gokul
1f3fee7373 Merge pull request #56 from bahdotsh/fix/windows-compatibility
fix(utils): add Windows support to fd module
2025-08-28 07:48:37 +05:30
bahdotsh
f49ccd70d9 fix(runtime): remove unnecessary borrow in Windows taskkill command
- Fix clippy needless_borrows_for_generic_args warning
- Change &pid.to_string() to pid.to_string() for taskkill /PID argument
- Ensure clippy passes with -D warnings on Windows builds
2025-08-27 15:45:58 +05:30
bahdotsh
5161882989 fix(utils): remove unused imports to fix Windows clippy warnings
- Remove unused io::self import from common scope
- Remove unused std::fs::OpenOptions and std::io::Write from windows_impl
- Add std::io import to unix_impl to fix io::Error references
- Ensure clippy passes with -D warnings on all platforms
2025-08-27 15:39:52 +05:30
bahdotsh
5e9658c885 ci: add Windows to build matrix and integration tests
- Add windows-latest to OS matrix with x86_64-pc-windows-msvc target
- Add dedicated Windows integration test job
- Verify Windows executable functionality
- Ensure cross-platform compatibility testing

This ensures Windows build issues are caught early in CI/CD pipeline.
2025-08-27 15:37:15 +05:30
bahdotsh
aa9da33b30 docs(utils): update README to document cross-platform fd behavior
- Document Unix vs Windows fd redirection limitations
- Update example to reflect platform-specific behavior
- Clarify that stderr suppression is Unix-only
2025-08-27 15:36:51 +05:30
bahdotsh
dff3697052 fix(utils): add Windows support to fd module
- Add conditional compilation for Unix/Windows platforms
- Move nix dependency to Unix-only target dependency
- Implement Windows-compatible fd redirection API
- Preserve full functionality on Unix systems
- Add comprehensive documentation for platform differences

Resolves Windows build errors:
- E0433: could not find 'sys' in 'nix'
- E0432: unresolved import 'nix::fcntl'
- E0433: could not find 'unix' in 'os'
- E0432: unresolved import 'nix::unistd'

Closes #43
2025-08-27 15:36:23 +05:30
7 changed files with 149 additions and 77 deletions

View File

@@ -3,7 +3,7 @@ name: Build
on:
workflow_dispatch:
push:
branches: [ main ]
branches: [main]
pull_request:
jobs:
@@ -12,12 +12,14 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- name: Checkout code
@@ -31,27 +33,27 @@ jobs:
target: ${{ matrix.target }}
override: true
components: clippy, rustfmt
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: -- --check
- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --target ${{ matrix.target }}
- name: Run tests
uses: actions-rs/cargo@v1
with:
command: test
args: --target ${{ matrix.target }}
args: --target ${{ matrix.target }}

26
Cargo.lock generated
View File

@@ -3080,7 +3080,7 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "wrkflw"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"bollard",
"chrono",
@@ -3127,7 +3127,7 @@ dependencies = [
[[package]]
name = "wrkflw-evaluator"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"colored",
"serde_yaml",
@@ -3137,7 +3137,7 @@ dependencies = [
[[package]]
name = "wrkflw-executor"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"async-trait",
"bollard",
@@ -3169,7 +3169,7 @@ dependencies = [
[[package]]
name = "wrkflw-github"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"lazy_static",
"regex",
@@ -3183,7 +3183,7 @@ dependencies = [
[[package]]
name = "wrkflw-gitlab"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"lazy_static",
"regex",
@@ -3198,7 +3198,7 @@ dependencies = [
[[package]]
name = "wrkflw-logging"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"chrono",
"once_cell",
@@ -3209,7 +3209,7 @@ dependencies = [
[[package]]
name = "wrkflw-matrix"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"indexmap 2.10.0",
"serde",
@@ -3220,7 +3220,7 @@ dependencies = [
[[package]]
name = "wrkflw-models"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"serde",
"serde_json",
@@ -3230,7 +3230,7 @@ dependencies = [
[[package]]
name = "wrkflw-parser"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"jsonschema",
"serde",
@@ -3244,7 +3244,7 @@ dependencies = [
[[package]]
name = "wrkflw-runtime"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"async-trait",
"futures",
@@ -3293,7 +3293,7 @@ dependencies = [
[[package]]
name = "wrkflw-ui"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"chrono",
"crossterm 0.26.1",
@@ -3315,7 +3315,7 @@ dependencies = [
[[package]]
name = "wrkflw-utils"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"nix",
"serde",
@@ -3325,7 +3325,7 @@ dependencies = [
[[package]]
name = "wrkflw-validators"
version = "0.7.1"
version = "0.7.2"
dependencies = [
"serde",
"serde_yaml",

View File

@@ -5,7 +5,7 @@ members = [
resolver = "2"
[workspace.package]
version = "0.7.1"
version = "0.7.2"
edition = "2021"
description = "A GitHub Actions workflow validator and executor"
documentation = "https://github.com/bahdotsh/wrkflw"

View File

@@ -793,7 +793,7 @@ async fn cleanup_processes() {
let _ = Command::new("taskkill")
.arg("/F")
.arg("/PID")
.arg(&pid.to_string())
.arg(pid.to_string())
.output();
}

View File

@@ -17,4 +17,6 @@ wrkflw-models = { path = "../models", version = "0.7.0" }
# External dependencies
serde.workspace = true
serde_yaml.workspace = true
[target.'cfg(unix)'.dependencies]
nix.workspace = true

View File

@@ -3,7 +3,7 @@
Shared helpers used across crates.
- Workflow file detection (`.github/workflows/*.yml`, `.gitlab-ci.yml`)
- File-descriptor redirection utilities for silencing noisy subprocess output
- File-descriptor redirection utilities for silencing noisy subprocess output (Unix only; Windows support is limited)
### Example
@@ -14,7 +14,7 @@ use wrkflw_utils::{is_workflow_file, fd::with_stderr_to_null};
assert!(is_workflow_file(Path::new(".github/workflows/ci.yml")));
let value = with_stderr_to_null(|| {
eprintln!("this is hidden");
eprintln!("this is hidden on Unix, visible on Windows");
42
}).unwrap();
assert_eq!(value, 42);

View File

@@ -35,78 +35,145 @@ pub fn is_workflow_file(path: &Path) -> bool {
}
/// Module for safely handling file descriptor redirection
///
/// On Unix systems (Linux, macOS), this module provides true file descriptor
/// redirection by duplicating stderr and redirecting it to /dev/null.
///
/// On Windows systems, the redirection functionality is limited due to platform
/// differences in file descriptor handling. The functions will execute without
/// error but stderr may not be fully suppressed.
pub mod fd {
use nix::fcntl::{open, OFlag};
use nix::sys::stat::Mode;
use nix::unistd::{close, dup, dup2};
use std::io::{self, Result};
use std::os::unix::io::RawFd;
use std::path::Path;
/// Standard file descriptors
const STDERR_FILENO: RawFd = 2;
use std::io::Result;
/// Represents a redirected stderr that can be restored
pub struct RedirectedStderr {
original_fd: Option<RawFd>,
null_fd: Option<RawFd>,
#[cfg(unix)]
original_fd: Option<std::os::unix::io::RawFd>,
#[cfg(unix)]
null_fd: Option<std::os::unix::io::RawFd>,
#[cfg(windows)]
_phantom: std::marker::PhantomData<()>,
}
impl RedirectedStderr {
/// Creates a new RedirectedStderr that redirects stderr to /dev/null
pub fn to_null() -> Result<Self> {
// Duplicate the current stderr fd
let stderr_backup = match dup(STDERR_FILENO) {
Ok(fd) => fd,
Err(e) => return Err(io::Error::other(e)),
};
#[cfg(unix)]
mod unix_impl {
use super::*;
use nix::fcntl::{open, OFlag};
use nix::sys::stat::Mode;
use nix::unistd::{close, dup, dup2};
use std::io;
use std::os::unix::io::RawFd;
use std::path::Path;
// Open /dev/null
let null_fd = match open(Path::new("/dev/null"), OFlag::O_WRONLY, Mode::empty()) {
Ok(fd) => fd,
Err(e) => {
/// Standard file descriptors
const STDERR_FILENO: RawFd = 2;
impl RedirectedStderr {
/// Creates a new RedirectedStderr that redirects stderr to /dev/null
pub fn to_null() -> Result<Self> {
// Duplicate the current stderr fd
let stderr_backup = match dup(STDERR_FILENO) {
Ok(fd) => fd,
Err(e) => return Err(io::Error::other(e)),
};
// Open /dev/null
let null_fd = match open(Path::new("/dev/null"), OFlag::O_WRONLY, Mode::empty()) {
Ok(fd) => fd,
Err(e) => {
let _ = close(stderr_backup); // Clean up on error
return Err(io::Error::other(e));
}
};
// Redirect stderr to /dev/null
if let Err(e) = dup2(null_fd, STDERR_FILENO) {
let _ = close(stderr_backup); // Clean up on error
let _ = close(null_fd);
return Err(io::Error::other(e));
}
};
// Redirect stderr to /dev/null
if let Err(e) = dup2(null_fd, STDERR_FILENO) {
let _ = close(stderr_backup); // Clean up on error
let _ = close(null_fd);
return Err(io::Error::other(e));
Ok(RedirectedStderr {
original_fd: Some(stderr_backup),
null_fd: Some(null_fd),
})
}
Ok(RedirectedStderr {
original_fd: Some(stderr_backup),
null_fd: Some(null_fd),
})
}
}
impl Drop for RedirectedStderr {
/// Automatically restores stderr when the RedirectedStderr is dropped
fn drop(&mut self) {
if let Some(orig_fd) = self.original_fd.take() {
// Restore the original stderr
let _ = dup2(orig_fd, STDERR_FILENO);
let _ = close(orig_fd);
}
impl Drop for RedirectedStderr {
/// Automatically restores stderr when the RedirectedStderr is dropped
fn drop(&mut self) {
if let Some(orig_fd) = self.original_fd.take() {
// Restore the original stderr
let _ = dup2(orig_fd, STDERR_FILENO);
let _ = close(orig_fd);
}
// Close the null fd
if let Some(null_fd) = self.null_fd.take() {
let _ = close(null_fd);
// Close the null fd
if let Some(null_fd) = self.null_fd.take() {
let _ = close(null_fd);
}
}
}
}
/// Run a function with stderr redirected to /dev/null, then restore stderr
#[cfg(windows)]
mod windows_impl {
use super::*;
impl RedirectedStderr {
/// Creates a new RedirectedStderr that redirects stderr to NUL on Windows
pub fn to_null() -> Result<Self> {
// On Windows, we can't easily redirect stderr at the file descriptor level
// like we can on Unix systems. This is a simplified implementation that
// doesn't actually redirect but provides the same interface.
// The actual stderr suppression will need to be handled differently on Windows.
Ok(RedirectedStderr {
_phantom: std::marker::PhantomData,
})
}
}
impl Drop for RedirectedStderr {
/// No-op drop implementation for Windows
fn drop(&mut self) {
// Nothing to restore on Windows in this simplified implementation
}
}
}
/// Run a function with stderr redirected to /dev/null (Unix) or suppressed (Windows), then restore stderr
///
/// # Platform Support
/// - **Unix (Linux, macOS)**: Fully supported - stderr is redirected to /dev/null
/// - **Windows**: Limited support - function executes but stderr may be visible
///
/// # Example
/// ```
/// use wrkflw_utils::fd::with_stderr_to_null;
///
/// let result = with_stderr_to_null(|| {
/// eprintln!("This will be hidden on Unix");
/// 42
/// }).unwrap();
/// assert_eq!(result, 42);
/// ```
pub fn with_stderr_to_null<F, T>(f: F) -> Result<T>
where
F: FnOnce() -> T,
{
let _redirected = RedirectedStderr::to_null()?;
Ok(f())
#[cfg(unix)]
{
let _redirected = RedirectedStderr::to_null()?;
Ok(f())
}
#[cfg(windows)]
{
// On Windows, we can't easily redirect stderr at the FD level,
// so we just run the function without redirection.
// This means stderr won't be suppressed on Windows, but the function will work.
Ok(f())
}
}
}
@@ -116,15 +183,16 @@ mod tests {
#[test]
fn test_fd_redirection() {
// This test will write to stderr, which should be redirected
// This test will write to stderr, which should be redirected on Unix
// On Windows, it will just run normally without redirection
let result = fd::with_stderr_to_null(|| {
// This would normally appear in stderr
eprintln!("This should be redirected to /dev/null");
// This would normally appear in stderr (suppressed on Unix, visible on Windows)
eprintln!("This should be redirected to /dev/null on Unix");
// Return a test value to verify the function passes through the result
42
});
// The function should succeed and return our test value
// The function should succeed and return our test value on both platforms
assert!(result.is_ok());
assert_eq!(result.unwrap(), 42);
}