Skip to content
Open
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
29 changes: 29 additions & 0 deletions packages/nx/schemas/nx-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,9 @@
"cache": {
"$ref": "#/definitions/targetDefaultsConfig/properties/cache"
},
"sandbox": {
"$ref": "#/definitions/targetDefaultsConfig/properties/sandbox"
},
"syncGenerators": {
"$ref": "#/definitions/targetDefaultsConfig/properties/syncGenerators"
}
Expand Down Expand Up @@ -1046,6 +1049,32 @@
"type": "boolean",
"description": "Specifies if the given target should be cacheable"
},
"sandbox": {
"type": "object",
"description": "Configures observed-IO sandboxing for tasks of this target",
"properties": {
"enabled": {
"type": "boolean",
"default": true,
"description": "Whether tasks for this target are tracked by the sandbox"
},
"ignoredReads": {
"type": "array",
"items": {
"type": "string"
},
"description": "Workspace-relative glob patterns for reads excluded from sandboxing reports"
},
"ignoredWrites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Workspace-relative glob patterns for writes excluded from sandboxing reports"
}
},
"additionalProperties": false
},
"syncGenerators": {
"type": "array",
"items": {
Expand Down
26 changes: 26 additions & 0 deletions packages/nx/schemas/project-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,32 @@
"type": "boolean",
"description": "Specifies if the given target should be cacheable"
},
"sandbox": {
"type": "object",
"description": "Configures observed-IO sandboxing for tasks of this target",
"properties": {
"enabled": {
"type": "boolean",
"default": true,
"description": "Whether tasks for this target are tracked by the sandbox"
},
"ignoredReads": {
"type": "array",
"items": {
"type": "string"
},
"description": "Workspace-relative glob patterns for reads excluded from sandboxing reports"
},
"ignoredWrites": {
"type": "array",
"items": {
"type": "string"
},
"description": "Workspace-relative glob patterns for writes excluded from sandboxing reports"
}
},
"additionalProperties": false
},
"continuous": {
"type": "boolean",
"default": false,
Expand Down
8 changes: 7 additions & 1 deletion packages/nx/src/config/task-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@
* `packages/nx/src/native/tasks/types.rs` and exposed to TypeScript via NAPI.
* This file re-exports them so existing imports keep working.
*/
export type { Task, TaskGraph, TaskTarget, TaskHashDetails } from '../native';
export type {
Task,
TaskGraph,
TaskTarget,
TaskHashDetails,
TaskSandboxConfiguration,
} from '../native';
15 changes: 14 additions & 1 deletion packages/nx/src/config/workspace-json-project-json.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { JsonInput } from '../native';
import type { JsonInput, TaskSandboxConfiguration } from '../native';
import type { PackageJson } from '../utils/package-json';
import type {
NxJsonConfiguration,
Expand Down Expand Up @@ -174,6 +174,14 @@ export interface TargetMetadata {
};
}

/**
* Configuration for observed-IO sandboxing of a target's tasks.
*
* The same shape rides on each Task instance (`Task['sandbox']`), so the
* type is shared with the native task definition.
*/
export type TargetSandboxConfiguration = TaskSandboxConfiguration;

export interface TargetDependencyConfig {
/**
* A list of projects that have `target`.
Expand Down Expand Up @@ -273,6 +281,11 @@ export interface TargetConfiguration<T = any> {
*/
cache?: boolean;

/**
* Configures observed-IO sandboxing for tasks of this target.
*/
sandbox?: TargetSandboxConfiguration;

/**
* Metadata about the target
*/
Expand Down
22 changes: 22 additions & 0 deletions packages/nx/src/native/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,8 @@ export interface Task {
parallelism?: boolean
/** This denotes if the task runs continuously */
continuous?: boolean
/** The target's observed-IO sandbox configuration, if declared */
sandbox?: TaskSandboxConfiguration
}

/** Graph of Tasks to be executed */
Expand Down Expand Up @@ -789,6 +791,26 @@ export interface TaskRun {
end: number
}

/** Observed-IO sandbox configuration of a task's target */
export interface TaskSandboxConfiguration {
/**
* Whether tasks for this target are tracked by the sandbox.
* Defaults to true. When false, no IO tracing is reported for the
* task, so no sandbox report is produced.
*/
enabled?: boolean
/**
* Workspace-relative glob patterns for reads that should be excluded
* from sandboxing reports.
*/
ignoredReads?: Array<string>
/**
* Workspace-relative glob patterns for writes that should be excluded
* from sandboxing reports.
*/
ignoredWrites?: Array<string>
}

export declare const enum TaskStatus {
Success = 0,
Failure = 1,
Expand Down
18 changes: 18 additions & 0 deletions packages/nx/src/native/tasks/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ pub struct Task {
pub parallelism: Option<bool>,
/// This denotes if the task runs continuously
pub continuous: Option<bool>,
/// The target's observed-IO sandbox configuration, if declared
pub sandbox: Option<TaskSandboxConfiguration>,
}

/// Observed-IO sandbox configuration of a task's target
#[napi(object)]
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct TaskSandboxConfiguration {
/// Whether tasks for this target are tracked by the sandbox.
/// Defaults to true. When false, no IO tracing is reported for the
/// task, so no sandbox report is produced.
pub enabled: Option<bool>,
/// Workspace-relative glob patterns for reads that should be excluded
/// from sandboxing reports.
pub ignored_reads: Option<Vec<String>>,
/// Workspace-relative glob patterns for writes that should be excluded
/// from sandboxing reports.
pub ignored_writes: Option<Vec<String>>,
}

impl Task {
Expand Down
24 changes: 24 additions & 0 deletions packages/nx/src/tasks-runner/create-task-graph.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,30 @@ describe('createTaskGraph', () => {
});
});

it('should copy the target sandbox configuration onto the task', () => {
projectGraph.nodes['app1'].data.targets['test'].sandbox = {
enabled: false,
ignoredReads: ['tmp/**'],
ignoredWrites: ['scratch/**'],
};

const taskGraph = createTaskGraph(
projectGraph,
{},
['app1', 'lib1'],
['test'],
undefined,
{}
);

expect(taskGraph.tasks['app1:test'].sandbox).toEqual({
enabled: false,
ignoredReads: ['tmp/**'],
ignoredWrites: ['scratch/**'],
});
expect(taskGraph.tasks['lib1:test'].sandbox).toBeUndefined();
});

it('should return tasks with outputs', () => {
projectGraph.nodes.app1.data.targets.test.outputs = [
'{workspaceRoot}/dist/app1',
Expand Down
1 change: 1 addition & 0 deletions packages/nx/src/tasks-runner/create-task-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ export class ProcessTasks {
cache: project.data.targets[target].cache ?? false,
parallelism: project.data.targets[target].parallelism ?? true,
continuous: project.data.targets[target].continuous ?? false,
sandbox: project.data.targets[target].sandbox,
};
}

Expand Down
59 changes: 59 additions & 0 deletions packages/nx/src/tasks-runner/task-io-service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { getTaskIOService, TaskPidUpdate } from './task-io-service';

describe('TaskIOService sandbox configuration', () => {
it('notifies PID subscribers for tasks without a sandbox configuration', () => {
const service = getTaskIOService();
const updates: TaskPidUpdate[] = [];
service.subscribeToTaskPids((update) => updates.push(update));

service.notifyPidUpdate({ taskId: 'proj:tracked', pid: 100 });

expect(updates).toEqual([{ taskId: 'proj:tracked', pid: 100 }]);
});

it('suppresses PID updates for tasks whose sandbox is disabled', () => {
const service = getTaskIOService();
const updates: TaskPidUpdate[] = [];
service.subscribeToTaskPids((update) => updates.push(update));

service.registerTaskSandboxConfiguration('proj:disabled', {
enabled: false,
});
service.notifyPidUpdate({ taskId: 'proj:disabled', pid: 200 });
service.notifyPidUpdate({ taskId: 'proj:other', pid: 201 });

expect(updates).toEqual([{ taskId: 'proj:other', pid: 201 }]);
expect(service.isTaskSandboxDisabled('proj:disabled')).toBe(true);
});

it('keeps PID updates for a sandbox configuration without enabled: false', () => {
const service = getTaskIOService();
const updates: TaskPidUpdate[] = [];
service.subscribeToTaskPids((update) => updates.push(update));

service.registerTaskSandboxConfiguration('proj:ignores-only', {
ignoredReads: ['tmp/**'],
});
service.notifyPidUpdate({ taskId: 'proj:ignores-only', pid: 300 });

expect(updates).toEqual([{ taskId: 'proj:ignores-only', pid: 300 }]);
expect(service.isTaskSandboxDisabled('proj:ignores-only')).toBe(false);
});

it('re-enables PID updates when a task is re-registered as enabled', () => {
const service = getTaskIOService();
const updates: TaskPidUpdate[] = [];
service.subscribeToTaskPids((update) => updates.push(update));

service.registerTaskSandboxConfiguration('proj:reenabled', {
enabled: false,
});
service.notifyPidUpdate({ taskId: 'proj:reenabled', pid: 400 });
service.registerTaskSandboxConfiguration('proj:reenabled', {
enabled: true,
});
service.notifyPidUpdate({ taskId: 'proj:reenabled', pid: 401 });

expect(updates).toEqual([{ taskId: 'proj:reenabled', pid: 401 }]);
});
});
33 changes: 33 additions & 0 deletions packages/nx/src/tasks-runner/task-io-service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { TargetSandboxConfiguration } from '../config/workspace-json-project-json';
import { getProcessMetricsService } from './process-metrics-service';

/**
Expand Down Expand Up @@ -50,6 +51,34 @@ class TaskIOService {
private taskInputCallbacks: TaskInputCallback[] = [];
private taskOutputsCallbacks: TaskOutputsCallback[] = [];

/**
* Task IDs whose target opted out of sandboxing via
* `sandbox: { enabled: false }`. PID updates for these tasks are
* suppressed so no IO tracing (and therefore no sandbox report) is
* produced for them.
*/
private sandboxDisabledTaskIds = new Set<string>();

/**
* Register the sandbox configuration of a task before it runs.
* Only the disabled state is retained; a task without a registered
* config is treated as sandbox-enabled.
*/
registerTaskSandboxConfiguration(
taskId: string,
config: TargetSandboxConfiguration | undefined
): void {
if (config?.enabled === false) {
this.sandboxDisabledTaskIds.add(taskId);
} else {
this.sandboxDisabledTaskIds.delete(taskId);
}
}

isTaskSandboxDisabled(taskId: string): boolean {
return this.sandboxDisabledTaskIds.has(taskId);
}

/**
* Subscribe to task PID updates.
* Receives notifications when processes are added/removed from tasks.
Expand Down Expand Up @@ -131,9 +160,13 @@ class TaskIOService {

/**
* Registers a PID to a task and notifies subscribers.
* No-op for tasks whose target disabled sandboxing.
* @param update The TaskPidUpdate containing taskId and pid.
*/
notifyPidUpdate(update: TaskPidUpdate): void {
if (this.sandboxDisabledTaskIds.has(update.taskId)) {
return;
}
for (const cb of this.pidCallbacks) {
try {
cb(update);
Expand Down
10 changes: 10 additions & 0 deletions packages/nx/src/tasks-runner/task-orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
getTaskSpecificEnv,
} from './task-env';
import { TaskStatus } from './tasks-runner';
import { getTaskIOService } from './task-io-service';
import { Batch, TasksSchedule } from './tasks-schedule';
import {
calculateReverseDeps,
Expand Down Expand Up @@ -414,6 +415,15 @@ export class TaskOrchestrator {
// region Processing Scheduled Tasks
private async processTask(taskId: string): Promise<NodeJS.ProcessEnv> {
const task = this.taskGraph.tasks[taskId];
if (task.sandbox) {
// Suppresses PID reporting for tasks whose target opted out of
// sandboxing (`sandbox: { enabled: false }`), so no sandbox report is
// produced for them. Both run paths await processTask before spawning.
getTaskIOService().registerTaskSandboxConfiguration(
task.id,
task.sandbox
);
}
const taskSpecificEnv = getTaskSpecificEnv(task, this.projectGraph);

if (!task.hash) {
Expand Down
Loading