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
48 changes: 48 additions & 0 deletions bazel-jdt-bridge/crates/bazel-jdt-core/src/jni_exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,54 @@ pub extern "system" fn Java_com_bazel_jdt_BazelBridge_nativeSyncIncremental(
}
}

#[no_mangle]
pub extern "system" fn Java_com_bazel_jdt_BazelBridge_nativeGetReverseDepsInProjects(
mut env: JNIEnv,
_class: JClass,
handle: jlong,
target_labels: JObjectArray,
) -> jobjectArray {
let state = match get_state(&mut env, handle) {
Some(s) => s,
None => return std::ptr::null_mut(),
};

let labels = match parse_java_string_array(&mut env, &target_labels) {
Some(l) => l,
None => {
return match create_string_array(&mut env, &[]) {
Ok(arr) => arr,
Err(_) => std::ptr::null_mut(),
}
}
};

let graph = state.graph.lock().unwrap_or_else(|e| e.into_inner());

let mut all_rdeps: std::collections::HashSet<String> = std::collections::HashSet::new();
for label in &labels {
let rdeps = graph.reverse_transitive_deps(label);
all_rdeps.extend(rdeps);
}
for label in &labels {
all_rdeps.remove(label);
}

let mut result: Vec<String> = all_rdeps.into_iter().collect();
result.sort();

log::info!(
"Reverse deps for {} targets: {} rdep targets",
labels.len(),
result.len()
);

match create_string_array(&mut env, &result) {
Ok(arr) => arr,
Err(_) => std::ptr::null_mut(),
}
}

#[no_mangle]
pub extern "system" fn Java_com_bazel_jdt_BazelBridge_nativeGetAspectBuildStats(
mut env: JNIEnv,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,23 @@ public String[] getTransitiveWorkspaceDeps(String[] targetLabels) {
}
}

public String[] getReverseDepsInProjects(String[] targetLabels) {
long h = snapshotHandle();
try {
return jniExecutor.submit(() -> nativeGetReverseDepsInProjects(h, targetLabels))
.get(JNI_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted during getReverseDepsInProjects", e);
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) throw (RuntimeException) cause;
throw new RuntimeException("getReverseDepsInProjects failed", cause);
} catch (TimeoutException e) {
throw new RuntimeException("getReverseDepsInProjects timed out", e);
}
}

public String[] syncIncremental(String[] changedFilePaths) {
long h = snapshotHandle();
try {
Expand Down Expand Up @@ -388,6 +405,7 @@ private long snapshotHandleNullable() {
private native void nativeCleanCache(long handle);
private native String[] nativeGetPendingChanges(long handle);
private native String[] nativeGetTransitiveWorkspaceDeps(long handle, String[] targetLabels);
private native String[] nativeGetReverseDepsInProjects(long handle, String[] targetLabels);
private native String[] nativeSyncIncremental(long handle, String[] changedFilePaths);
private native String nativeGetAspectBuildStats(long handle);
private native boolean nativeIsTestTarget(long handle, String targetLabel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ public static void refreshClasspathForFiles(List<String> changedFiles) {
* Used by incremental sync to update only affected targets.
*/
public static void refreshClasspathForTargets(List<String> targetLabels) {
refreshClasspathForTargets(targetLabels, false);
}

public static void refreshClasspathForTargets(List<String> targetLabels, boolean force) {
if (targetLabels == null || targetLabels.isEmpty()) return;
try {
org.eclipse.core.resources.IWorkspace workspace =
Expand All @@ -321,7 +325,7 @@ public static void refreshClasspathForTargets(List<String> targetLabels) {
}
}
for (IProject project : affectedProjects) {
setMergedClasspathContainer(project);
setMergedClasspathContainer(project, force);
}
} catch (Exception e) {
LOG.log(new Status(IStatus.ERROR, "com.bazel.jdt",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.bazel.jdt;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IProject;
Expand Down Expand Up @@ -47,6 +49,8 @@ public Object executeCommand(String commandId, List<Object> arguments, IProgress
return handleSetActiveDebugProject(arguments);
case "bazel-jdt.clearActiveDebugProject":
return handleClearActiveDebugProject();
case "bazel-jdt.partialSync":
return handlePartialSync(arguments);
default:
return null;
}
Expand Down Expand Up @@ -183,6 +187,87 @@ private void createProjectsForTargets(String workspacePath, Set<String> newTarge
}
}

private Object handlePartialSync(List<Object> arguments) {
try {
if (arguments.isEmpty() || !(arguments.get(0) instanceof String)) {
throw new IllegalArgumentException("Scope pattern required");
}
String scopePattern = (String) arguments.get(0);

BazelBridge bridge = BazelBridge.getInstance();
if (!bridge.isInitialized()) {
throw new IllegalStateException(
"Bazel project not imported yet. Import the project first.");
}

String syncMode = arguments.size() > 1 && arguments.get(1) instanceof String
? (String) arguments.get(1) : bridge.getSyncMode();
bridge.setSyncMode(syncMode);

LOG.log(new Status(IStatus.INFO, "com.bazel.jdt",
"Partial sync: querying targets for " + scopePattern));
String[] targets = bridge.queryTargets(new String[]{scopePattern});
if (targets == null || targets.length == 0) {
LOG.log(new Status(IStatus.INFO, "com.bazel.jdt",
"Partial sync: no targets found for " + scopePattern));
Map<String, Object> result = new HashMap<>();
result.put("refreshed", 0);
result.put("newTargets", new ArrayList<String>());
return result;
}

String[] rdeps = bridge.getReverseDepsInProjects(targets);
int rdepCount = rdeps != null ? rdeps.length : 0;
LOG.log(new Status(IStatus.INFO, "com.bazel.jdt",
"Partial sync: " + targets.length + " scope targets, " + rdepCount + " rdep targets"));

Set<String> mergedSet = new java.util.LinkedHashSet<>();
for (String t : targets) mergedSet.add(t);
if (rdeps != null) {
for (String r : rdeps) mergedSet.add(r);
}
String[] mergedTargets = mergedSet.toArray(new String[0]);

LOG.log(new Status(IStatus.INFO, "com.bazel.jdt",
"Partial sync: running aspect build for " + mergedTargets.length + " targets (scope + rdeps)"));
bridge.runAspectBuild(mergedTargets, bridge.getBuildFlags());

Set<String> existingTargetLabels = getExistingTargetLabels();
List<String> existingTargets = new ArrayList<>();
List<String> newTargets = new ArrayList<>();
for (String label : mergedTargets) {
if (!label.startsWith("//")) {
LOG.log(new Status(IStatus.WARNING, "com.bazel.jdt",
"Partial sync: skipping invalid label: " + label));
continue;
}
if (existingTargetLabels.contains(label)) {
existingTargets.add(label);
} else {
newTargets.add(label);
}
}

LOG.log(new Status(IStatus.INFO, "com.bazel.jdt",
"Partial sync: " + existingTargets.size() + " existing, "
+ newTargets.size() + " new targets"));

if (!existingTargets.isEmpty()) {
BazelClasspathManager.refreshClasspathForTargets(existingTargets, true);
}

Map<String, Object> result = new HashMap<>();
result.put("refreshed", existingTargets.size());
result.put("newTargets", newTargets);
result.put("rdeps", rdepCount);
return result;
} catch (Exception e) {
LOG.log(new Status(IStatus.ERROR, "com.bazel.jdt",
"Partial sync failed", e));
throw new RuntimeException("Partial sync failed: " + e.getMessage(), e);
}
}

private Object handleSyncProject(List<Object> arguments) {
try {
if (!arguments.isEmpty() && arguments.get(0) instanceof String) {
Expand Down
4 changes: 4 additions & 0 deletions bazel-jdt-bridge/java-bridge/src/main/resources/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
<command id="bazel-jdt.buildTarget"/>
<command id="bazel-jdt.setActiveDebugProject"/>
<command id="bazel-jdt.clearActiveDebugProject"/>
<command id="bazel-jdt.getDependencyPackages"/>
<command id="bazel-jdt.createProjectForPackage"/>
<command id="bazel-jdt.waitForIndexesReady"/>
<command id="bazel-jdt.partialSync"/>
</delegateCommandHandler>
</extension>

Expand Down
9 changes: 9 additions & 0 deletions bazel-jdt-bridge/vscode-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
{
"command": "bazel-jdt.addDirectoryToProject",
"title": "Bazel: Add to Project"
},
{
"command": "bazel-jdt.partialSync",
"title": "Bazel: Partially Sync Package"
}
],
"menus": {
Expand All @@ -46,6 +50,11 @@
"command": "bazel-jdt.addDirectoryToProject",
"when": "explorerResourceIsFolder",
"group": "bazel-jdt@1"
},
{
"command": "bazel-jdt.partialSync",
"when": "explorerResourceIsFolder",
"group": "bazel-jdt@2"
}
]
},
Expand Down
76 changes: 76 additions & 0 deletions bazel-jdt-bridge/vscode-extension/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { getConfig } from './config';
import { runImportWizard } from './importWizard';
import { parseBazelprojectFile, addDirectoryToBazelproject } from './bazelproject';

let syncInProgress = false;

export function registerImportCommand(context: vscode.ExtensionContext) {
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '';

Expand Down Expand Up @@ -77,12 +79,19 @@ export function registerAddDirectoryCommand(context: vscode.ExtensionContext, wo
export function registerRuntimeCommands(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('bazel-jdt.syncProject', async () => {
if (syncInProgress) {
vscode.window.showWarningMessage('A sync is already in progress.');
return;
}
syncInProgress = true;
try {
const config = getConfig();
await vscode.commands.executeCommand('java.execute.workspaceCommand',
'bazel-jdt.syncProject', config.dependencyResolution, config.dependencySourceLoading);
} catch (error) {
vscode.window.showErrorMessage(`Bazel sync failed: ${error}`);
} finally {
syncInProgress = false;
}
})
);
Expand Down Expand Up @@ -116,3 +125,70 @@ export function registerRuntimeCommands(context: vscode.ExtensionContext) {
})
);
}

export function registerPartialSyncCommand(context: vscode.ExtensionContext) {
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '';

context.subscriptions.push(
vscode.commands.registerCommand('bazel-jdt.partialSync', async (uri: vscode.Uri) => {
if (!uri) {
vscode.window.showWarningMessage('No folder selected.');
return;
}

if (syncInProgress) {
vscode.window.showWarningMessage('A sync is already in progress.');
return;
}

const dirPath = uri.fsPath;
const hasBuild = fs.existsSync(path.join(dirPath, 'BUILD'))
|| fs.existsSync(path.join(dirPath, 'BUILD.bazel'));
if (!hasBuild) {
vscode.window.showInformationMessage('No BUILD file found in this directory.');
return;
}

const relativePath = path.relative(workspaceRoot, dirPath).replace(/\\/g, '/');
const scopePattern = `//${relativePath}/...:all`;

syncInProgress = true;
try {
const config = getConfig();
const result = await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: `Partially syncing ${scopePattern}`,
cancellable: false,
},
async (progress) => {
progress.report({ message: 'Querying targets...' });
const syncResult = await vscode.commands.executeCommand(
'java.execute.workspaceCommand',
'bazel-jdt.partialSync', scopePattern, config.syncMode) as
{ refreshed?: number; newTargets?: string[] } | null;
return syncResult;
}
);

const newTargets = (result?.newTargets ?? []).filter(
(label: string) => label.startsWith('//') && label.includes(':'));

if (newTargets.length > 0) {
for (const targetLabel of newTargets) {
const packagePath = targetLabel.replace(/^\/\//, '').replace(/:.*$/, '');
await vscode.commands.executeCommand('java.execute.workspaceCommand',
'bazel-jdt.createProjectForPackage', workspaceRoot, config.bazelPath,
config.cacheDir, packagePath, targetLabel);
}
await vscode.commands.executeCommand('java.execute.workspaceCommand',
'bazel-jdt.syncProject', config.dependencyResolution, config.dependencySourceLoading);
}
} catch (error) {
vscode.window.showErrorMessage(`Partial sync failed: ${error}`);
} finally {
syncInProgress = false;
}
})
);
}
3 changes: 2 additions & 1 deletion bazel-jdt-bridge/vscode-extension/src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { registerImportCommand, registerRuntimeCommands, registerAddDirectoryCommand } from './commands';
import { registerImportCommand, registerRuntimeCommands, registerAddDirectoryCommand, registerPartialSyncCommand } from './commands';
import { BazelDebugConfigurationProvider } from './debugAdapter';
import { createStatusBar } from './statusBar';
import { getConfig } from './config';
Expand Down Expand Up @@ -36,6 +36,7 @@ function activateFull(context: vscode.ExtensionContext, workspaceRoot: string) {
registerImportCommand(context);
registerRuntimeCommands(context);
registerAddDirectoryCommand(context, workspaceRoot);
registerPartialSyncCommand(context);
context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider(
'java', new BazelDebugConfigurationProvider()
Expand Down
Loading