Skip to content
Draft
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
35 changes: 34 additions & 1 deletion .github/workflows/presubmit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,40 @@ jobs:
path: |
**/build/reports/
**/build/test-results/
# Job 4: Verify Plugin
# Job 4: WSL Dart SDK Tests
wsl-dart-tests:
needs: build-plugin
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'gradle'

- name: Install WSL with Ubuntu
run: wsl --install -d Ubuntu --no-launch

- name: Install Dart SDK in WSL
run: |
wsl -d Ubuntu -- sudo apt-get update
wsl -d Ubuntu -- sudo apt-get install -y wget gnupg2
wsl -d Ubuntu -- sh -c "wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo gpg --dearmor -o /usr/share/keyrings/dart.gpg"
wsl -d Ubuntu -- sh -c 'echo "deb [signed-by=/usr/share/keyrings/dart.gpg arch=amd64] https://storage.googleapis.com/dart-archive/channels/stable/release/latest/linux_packages/debian stable main" | sudo tee /etc/apt/sources.list.d/dart_stable.list > /dev/null'
wsl -d Ubuntu -- sudo apt-get update
wsl -d Ubuntu -- sudo apt-get install -y dart

- name: Run WSL Tests
run: ./gradlew :test --tests "com.jetbrains.lang.dart.sdk.DartSdkWslTest"
working-directory: third_party
env:
WSL_DART_SDK: "\\\\wsl$\\Ubuntu\\usr\\lib\\dart"

# Job 5: Verify Plugin
verify-plugin:
needs: build-plugin
strategy:
Expand Down
14 changes: 14 additions & 0 deletions third_party/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,20 @@ tasks {
} else {
logger.error("DART_HOME environment variable is not set. Dart Analysis Server tests will fail.")
}

// WSL Dart SDK for WSL-specific tests
val wslDartSdkPath = System.getenv("WSL_DART_SDK")
if (wslDartSdkPath != null) {
val versionFile = file("${wslDartSdkPath}/version")
if (versionFile.exists() && versionFile.isFile) {
jvmArgs("-Dwsl.dart.sdk=${wslDartSdkPath}")
} else {
logger.error("This directory, ${dartSdkPath}, doesn't appear to be Dart SDK path, " +
"no version file found at ${versionFile.absolutePath}")
}
} else {
logger.error("WSL_DART_SDK environment variable is not set. WSL-specific DartSdk tests will be skipped.")
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public static void scheduleDartPackageRootsUpdate(final @NotNull Project project
Map<String, String> packagesMap = null;
VirtualFile packagesFile = PackageConfigFileUtil.findPackageConfigJsonFile(pubspecFile.getParent());
if (packagesFile != null) {
packagesMap = PackageConfigFileUtil.getPackagesMapFromPackageConfigJsonFile(packagesFile);
packagesMap = PackageConfigFileUtil.getPackagesMapFromPackageConfigJsonFile(project, packagesFile);
}

if (packagesMap != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,26 @@ class DartStartupActivity : ProjectActivity {
}
}

/**
* Excludes build and tool cache folders (.dart_tool, .pub, build) from the module.
* This function performs a slow file index operation followed by a write action,
* so it should be called with appropriate threading considerations.
*
* For EDT-safe usage, call [prepareExcludeBuildAndToolCacheFolders] in a background
* thread with read access, then invoke the returned action on the EDT.
*/
fun excludeBuildAndToolCacheFolders(module: Module, pubspecYamlFile: VirtualFile) {
prepareExcludeBuildAndToolCacheFolders(module, pubspecYamlFile)?.invoke()
}

private fun prepareExcludeBuildAndToolCacheFolders(module: Module, pubspecYamlFile: VirtualFile): (() -> Unit)? {
/**
* Prepares the exclusion of build and tool cache folders.
* This performs a slow file index operation (ProjectFileIndex.getContentRootForFile)
* and should be called from a background thread with read access.
*
* @return A write action to execute on EDT, or null if no exclusions are needed.
*/
internal fun prepareExcludeBuildAndToolCacheFolders(module: Module, pubspecYamlFile: VirtualFile): (() -> Unit)? {
val root = pubspecYamlFile.parent ?: return null
val contentRoot = ProjectFileIndex.getInstance(module.project).getContentRootForFile(root) ?: return null
val rootUrl = root.url
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ public final class DartAnalysisServerService implements Disposable {
private @NotNull String myServerVersion = "";
private @NotNull String mySdkVersion = "";
private @Nullable String mySdkHome;
private @Nullable DartSdk mySdk;

private final DartServerRootsHandler myRootsHandler;
private final Map<String, Long> myFilePathWithOverlaidContentToTimestamp = Collections.synchronizedMap(new HashMap<>());
Expand Down Expand Up @@ -2107,9 +2108,10 @@ private void startServer(final @NotNull DartSdk sdk, boolean suppressAnalytics)
if (DartPubActionBase.isInProgress()) return; // DartPubActionBase will start the server itself when finished

synchronized (myLock) {
mySdk = sdk;
mySdkHome = sdk.getHomePath();

final String runtimePath = FileUtil.toSystemDependentName(DartSdkUtil.getDartExePath(sdk));
final String runtimePath = FileUtil.toSystemDependentName(sdk.getFullDartExePath());

// If true, then the DAS will be started via `dart language-server`, instead of `dart .../analysis_server.dart.snapshot`
final boolean useDartLangServerCall = isDartSdkVersionSufficientForDartLangServer(sdk);
Expand Down Expand Up @@ -2147,6 +2149,9 @@ else if (!useDartLangServerCall && !dasSnapshotFile.canRead()) {
//analysisServerPath =
// FileUtil.toSystemDependentName(localDartSdkPath + "pkg/analysis_server/bin/server.dart");

// Finally convert the analysisServerPath path to the local path to support running in WSL
analysisServerPath = sdk.getLocalFileUri(analysisServerPath);

final DebugPrintStream debugStream = str -> {
str = StringUtil.first(str, MAX_DEBUG_LOG_LINE_LENGTH, true);
synchronized (myDebugLog) {
Expand Down Expand Up @@ -2174,9 +2179,13 @@ else if (!useDartLangServerCall && !dasSnapshotFile.canRead()) {
// NOP
}

List<String> commandList = sdk.getDartSimpleCommandList();
String actualCommand = commandList.removeFirst();
commandList.addAll(StringUtil.split(vmArgsRaw, " "));

String firstArgument = useDartLangServerCall ? "language-server" : analysisServerPath;
myServerSocket =
new StdioServerSocket(runtimePath, StringUtil.split(vmArgsRaw, " "), firstArgument, StringUtil.split(serverArgsRaw, " "),
new StdioServerSocket(actualCommand, commandList, firstArgument, StringUtil.split(serverArgsRaw, " "),
debugStream);
myServerSocket.setClientId(getClientId());
myServerSocket.setClientVersion(getClientVersion());
Expand Down Expand Up @@ -2621,13 +2630,11 @@ private void server_setSubscriptions(@Nullable AnalysisServer server) {
* </ul>
*/
public String getFileUri(@NotNull VirtualFile file) {
if (!isDartSdkVersionSufficientForFileUri(mySdkVersion)) {
// prior to Dart SDK 3.4, the protocol required file paths instead of URIs
return FileUtil.toSystemDependentName(file.getPath());
DartSdk sdk = mySdk;
if(sdk == null) {
sdk = DartSdk.getDartSdk(myProject);
}

String fileUri = file.getUserData(DartFileInfoKt.DART_NOT_LOCAL_FILE_URI_KEY);
return fileUri != null ? fileUri : getLocalFileUri(file.getPath());
return sdk.getFileUri(file);
}

/**
Expand All @@ -2639,14 +2646,12 @@ public String getFileUri(@NotNull VirtualFile file) {
* @see #getFileUri(VirtualFile)
*/
public String getLocalFileUri(@NotNull String localFilePath) {
if (!isDartSdkVersionSufficientForFileUri(mySdkVersion)) {
// prior to Dart SDK 3.4, the protocol required file paths instead of URIs
return FileUtil.toSystemDependentName(localFilePath);
DartSdk sdk = mySdk;
if(sdk == null) {
sdk = DartSdk.getDartSdk(myProject);
}

String escapedPath = URLUtil.encodePath(FileUtil.toSystemIndependentName(localFilePath));
String url = VirtualFileManager.constructUrl(URLUtil.FILE_PROTOCOL, escapedPath);
URI uri = VfsUtil.toUri(url);
return uri != null ? uri.toString() : url;
return sdk.getLocalFileUri(localFilePath);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.OSAgnosticPathUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.jetbrains.lang.dart.sdk.DartSdk
import java.net.URI
import java.net.URISyntaxException

Expand All @@ -26,13 +27,15 @@ data class DartNotLocalFileInfo(private val project: Project, val fileUri: Strin


fun getDartFileInfo(project: Project, filePathOrUri: String): DartFileInfo = when {
!filePathOrUri.contains("://") -> DartLocalFileInfo(FileUtil.toSystemIndependentName(filePathOrUri))
!filePathOrUri.startsWith("file://") -> DartNotLocalFileInfo(project, filePathOrUri)
!filePathOrUri.contains("://") -> DartLocalFileInfo(FileUtil.toSystemIndependentName(getIDEFileName(project, filePathOrUri)))
!filePathOrUri.startsWith("file://") -> DartNotLocalFileInfo(project, getIDEFileName(project, filePathOrUri))
else -> try {
var path = URI(filePathOrUri).path
path = getIDEFileName(project, path)
if (SystemInfo.isWindows && path.length >= 3 && path[0] == '/' && OSAgnosticPathUtil.startsWithWindowsDrive(path.substring(1))) {
path = path.substring(1)
}
path = FileUtil.toSystemIndependentName(path)
DartLocalFileInfo(path)
}
catch (_: URISyntaxException) {
Expand All @@ -48,3 +51,13 @@ fun getDartFileInfo(project: Project, virtualFile: VirtualFile): DartFileInfo =
virtualFile.getUserData(DART_NOT_LOCAL_FILE_URI_KEY)
?.let { DartNotLocalFileInfo(project, it) }
?: DartLocalFileInfo(virtualFile.path)

fun getIDEFileName(project: Project, filePathOrUri: String): String {
val sdk = DartSdk.getDartSdk(project)
return sdk?.getIDEFilePath(filePathOrUri) ?: filePathOrUri
}

fun getIDEFileUri(project: Project, filePathOrUri: String): String {
val sdk = DartSdk.getDartSdk(project)
return sdk?.getLocalFileUri(filePathOrUri) ?: filePathOrUri
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import com.jetbrains.lang.dart.ide.toolingDaemon.DartToolingDaemonConsumer
import com.jetbrains.lang.dart.ide.toolingDaemon.DartToolingDaemonListener
import com.jetbrains.lang.dart.ide.toolingDaemon.DartToolingDaemonRequestHandler
import com.jetbrains.lang.dart.sdk.DartSdk
import com.jetbrains.lang.dart.sdk.DartSdkUtil
import de.roderick.weberknecht.WebSocket
import de.roderick.weberknecht.WebSocketEventHandler
import de.roderick.weberknecht.WebSocketException
Expand Down Expand Up @@ -124,7 +123,7 @@ class DTDProcess {

fun start(sdk: DartSdk) {
val commandLine = GeneralCommandLine().withWorkDirectory(sdk.homePath)
commandLine.exePath = FileUtil.toSystemDependentName(DartSdkUtil.getDartExePath(sdk))
commandLine.exePath = if (sdk.isWsl) sdk.dartExePath else FileUtil.toSystemDependentName(sdk.dartExePath)
commandLine.charset = StandardCharsets.UTF_8
commandLine.addParameter("tooling-daemon")
commandLine.addParameter("--machine")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,13 @@ import com.jetbrains.lang.dart.DartBundle
import com.jetbrains.lang.dart.analytics.Analytics
import com.jetbrains.lang.dart.analytics.AnalyticsData
import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService
import com.jetbrains.lang.dart.excludeBuildAndToolCacheFolders
import com.jetbrains.lang.dart.prepareExcludeBuildAndToolCacheFolders
import com.jetbrains.lang.dart.flutter.FlutterUtil
import com.jetbrains.lang.dart.ide.runner.DartConsoleFilter
import com.jetbrains.lang.dart.ide.runner.DartRelativePathsConsoleFilter
import com.jetbrains.lang.dart.sdk.DartConfigurable
import com.jetbrains.lang.dart.sdk.DartSdk
import com.jetbrains.lang.dart.sdk.DartSdkLibUtil
import com.jetbrains.lang.dart.sdk.DartSdkUtil
import com.jetbrains.lang.dart.util.PubspecYamlUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -107,7 +106,7 @@ abstract class DartPubActionBase : AnAction(), DumbAware {

if (sdk == null) return

val exeFile = File(DartSdkUtil.getDartExePath(sdk))
val exeFile = File(sdk.fullDartExePath)

if (!exeFile.isFile) {
if (allowModalDialogs) {
Expand Down Expand Up @@ -137,7 +136,7 @@ abstract class DartPubActionBase : AnAction(), DumbAware {
setupPubExePath(command, sdk)
command.addParameters(*pubParameters)

doPerformPubAction(module, pubspecYamlFile, command, getTitle(pubspecYamlFile))
doPerformPubActionAsync(module, pubspecYamlFile, command, getTitle(pubspecYamlFile))
}
}

Expand Down Expand Up @@ -191,7 +190,12 @@ abstract class DartPubActionBase : AnAction(), DumbAware {

@JvmStatic
fun setupPubExePath(commandLine: GeneralCommandLine, dartSdk: DartSdk) {
commandLine.withExePath(FileUtil.toSystemDependentName(DartSdkUtil.getDartExePath(dartSdk)))
if (dartSdk.isWsl) {
commandLine.setExePath(dartSdk.dartExePath)
}
else {
commandLine.setExePath(FileUtil.toSystemDependentName(dartSdk.dartExePath))
}
commandLine.addParameter("pub")
}

Expand Down Expand Up @@ -223,32 +227,66 @@ abstract class DartPubActionBase : AnAction(), DumbAware {
return null
}

private fun doPerformPubAction(
private fun doPerformPubActionAsync(
module: Module,
pubspecYamlFile: VirtualFile,
command: GeneralCommandLine,
actionTitle: @NlsContexts.NotificationTitle String,
) {
FileDocumentManager.getInstance().saveAllDocuments()
// Save documents on EDT first
ApplicationManager.getApplication().invokeLater {
FileDocumentManager.getInstance().saveAllDocuments()
}

// Run process creation on background thread to support WSL
// WSLDistribution.patchCommandLine requires a background thread
ApplicationManager.getApplication().executeOnPooledThread {
doPerformPubAction(module, pubspecYamlFile, command, actionTitle)
}
}

private fun doPerformPubAction(
module: Module,
pubspecYamlFile: VirtualFile,
command: GeneralCommandLine,
actionTitle: @NlsContexts.NotificationTitle String,
) {
try {
if (ourInProgress.compareAndSet(false, true)) {
command.withEnvironment(PUB_ENV_VAR_NAME, pubEnvValue)

val sdk = DartSdk.getDartSdk(module.getProject())
sdk?.patchCommandLineIfRequired(command)

val processHandler = OSProcessHandler(command)

processHandler.addProcessListener(object : ProcessAdapter() {
override fun processTerminated(event: ProcessEvent) {
ourInProgress.set(false)

ApplicationManager.getApplication().invokeLater {
if (!module.isDisposed) {
excludeBuildAndToolCacheFolders(module, pubspecYamlFile)
// Use coroutine to run slow file index operation in background, then finish on EDT
module.project.service<DartPubActionsService>().cs.launch {
if (module.isDisposed) return@launch

// prepareExcludeBuildAndToolCacheFolders uses ProjectFileIndex.getContentRootForFile
// which is a slow operation requiring read access
val excludeAction = readAction {
prepareExcludeBuildAndToolCacheFolders(module, pubspecYamlFile)
}

withContext(Dispatchers.EDT) {
if (module.isDisposed) return@withContext

excludeAction?.invoke()

// refresh later than exclude, otherwise IDE may start indexing excluded folders
VfsUtil.markDirtyAndRefresh(true, true, true, pubspecYamlFile.parent)
}

if (DartSdkLibUtil.isDartSdkEnabled(module)) {
// serverReadyForRequest requires read access and does blocking operations,
// so it must run on a background thread with read access
if (!module.isDisposed && DartSdkLibUtil.isDartSdkEnabled(module)) {
readAction {
DartAnalysisServerService.getInstance(module.project).serverReadyForRequest()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.io.BaseOutputReader
import com.jetbrains.lang.dart.logging.PluginLogger
import com.jetbrains.lang.dart.sdk.DartSdk
import com.jetbrains.lang.dart.sdk.DartSdkUtil
import java.nio.charset.StandardCharsets

@Service(Service.Level.PROJECT)
Expand All @@ -39,7 +38,7 @@ class DartDevToolsService(private val myProject: Project) : Disposable {

val commandLine = GeneralCommandLine().withWorkDirectory(sdk.homePath)
commandLine.charset = StandardCharsets.UTF_8
commandLine.exePath = FileUtil.toSystemDependentName(DartSdkUtil.getDartExePath(sdk))
commandLine.exePath = if (sdk.isWsl) sdk.dartExePath else FileUtil.toSystemDependentName(sdk.dartExePath)
commandLine.addParameter("devtools")
commandLine.addParameter("--machine")
dtdUri?.let { commandLine.addParameter("--dtd-uri=$it") }
Expand Down
Loading