Repository: intellij-rust/intellij-rust
Issue: Root "target" directory is not excluded in multi-crates cargo workspace

## Environment

* **Intellij-Rust plugin version:** v0.2.96.2122-192
* **Rust toolchain version:** 1.33.0
* **IDE name and version:** CLion 2019.1
* **Operating system:** Linux 5.0.7 (ArchLinux)

## Problem description

When opening a Cargo workspace with multiple crates in it but no "root crate", Cargo generates a single `target` directory at the root of the workspace, but it is not automatically marked as excluded by CLion, and it gets indexed.

This is especially annoying in CLion, as the "Mark directory as excluded" option is not present due to [this issue](https://youtrack.jetbrains.com/issue/CPP-13494), and you have to edit the `.iml` file manually.

## Steps to reproduce

This can be reproducing by opening [this project I'm working on currently](https://gitlab.com/prokopyl/lv2-rs), in CLion.
Running `cargo build` creates a `target` directory at the root of the workspace, but it isn't marked as excluded and CLion starts to index it. According to the generated `.iml` project file, it seems the Rust plugin expects a `target` directory inside each sub-crate instead:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
  <component name="NewModuleRootManager">
    <content url="file://$MODULE_DIR$">
      <!-- [...] a whole bunch of source folder declarations are here -->
      <excludeFolder url="file://$MODULE_DIR$/lilv-sys/target" />
      <excludeFolder url="file://$MODULE_DIR$/lilv/target" />
      <excludeFolder url="file://$MODULE_DIR$/lilv/tests/lv2-amp-raw/target" />
      <excludeFolder url="file://$MODULE_DIR$/lilv/tests/lv2-amp/target" />
      <excludeFolder url="file://$MODULE_DIR$/lilv/tests/lv2-midigate-raw/target" />
      <excludeFolder url="file://$MODULE_DIR$/lilv/tests/lv2-midigate/target" />
      <excludeFolder url="file://$MODULE_DIR$/lv2-derive/target" />
      <excludeFolder url="file://$MODULE_DIR$/lv2-plugin-derive/target" />
      <excludeFolder url="file://$MODULE_DIR$/lv2-plugin-test/target" />
      <excludeFolder url="file://$MODULE_DIR$/lv2-plugin/examples/lv2-amp/target" />
      <excludeFolder url="file://$MODULE_DIR$/lv2-plugin/target" />
      <excludeFolder url="file://$MODULE_DIR$/lv2-sys/target" />
      <excludeFolder url="file://$MODULE_DIR$/lv2/target" />
      <!-- <excludeFolder url="file://$MODULE_DIR$/target" /> This is what I need to manually add to exclude the right directory -->
    </content>
    <orderEntry type="inheritedJdk" />
    <orderEntry type="sourceFolder" forTests="false" />
  </component>
</module>
```


Changed files:
src/test/kotlin/org/rustSlowTests/RsContentRootsTest.kt, src/main/kotlin/org/rust/cargo/project/model/impl/CargoProjectImpl.kt

diff --git a/src/main/kotlin/org/rust/cargo/project/model/impl/CargoProjectImpl.kt b/src/main/kotlin/org/rust/cargo/project/model/impl/CargoProjectImpl.kt
index ae06a2a0f..7b8529635 100644
--- a/src/main/kotlin/org/rust/cargo/project/model/impl/CargoProjectImpl.kt
+++ b/src/main/kotlin/org/rust/cargo/project/model/impl/CargoProjectImpl.kt
@@ -16,12 +16,14 @@ import com.intellij.openapi.components.State
 import com.intellij.openapi.module.ModuleUtilCore
 import com.intellij.openapi.progress.BackgroundTaskQueue
 import com.intellij.openapi.project.Project
+import com.intellij.openapi.roots.ContentEntry
 import com.intellij.openapi.roots.ModuleRootManager
 import com.intellij.openapi.roots.ModuleRootModificationUtil
 import com.intellij.openapi.roots.ex.ProjectRootManagerEx
 import com.intellij.openapi.util.EmptyRunnable
 import com.intellij.openapi.util.Key
 import com.intellij.openapi.util.UserDataHolderBase
+import com.intellij.openapi.util.io.FileUtil
 import com.intellij.openapi.vfs.LocalFileSystem
 import com.intellij.openapi.vfs.VirtualFile
 import com.intellij.openapi.vfs.VirtualFileManager
@@ -31,6 +33,7 @@ import com.intellij.util.io.exists
 import com.intellij.util.io.systemIndependentPath
 import org.jdom.Element
 import org.jetbrains.annotations.TestOnly
+import org.rust.cargo.CargoConstants
 import org.rust.cargo.project.model.CargoProject
 import org.rust.cargo.project.model.CargoProject.UpdateStatus
 import org.rust.cargo.project.model.CargoProjectsService
@@ -404,17 +407,16 @@ private fun setupProjectRoots(project: Project, cargoProjects: List<CargoProject
             if (project.isDisposed) return@runWriteAction
             ProjectRootManagerEx.getInstanceEx(project).mergeRootsChangesDuring {
                 for (cargoProject in cargoProjects) {
+                    cargoProject.workspaceRootDir?.setupContentRoots(project) { contentRoot ->
+                        addExcludeFolder(FileUtil.join(contentRoot.url, CargoConstants.ProjectLayout.target))
+                    }
+
                     val workspacePackages = cargoProject.workspace?.packages
                         .orEmpty()
                         .filter { it.origin == PackageOrigin.WORKSPACE }
 
                     for (pkg in workspacePackages) {
-                        val packageContentRoot = pkg.contentRoot ?: continue
-                        val packageModule = ModuleUtilCore.findModuleForFile(packageContentRoot, project) ?: continue
-                        ModuleRootModificationUtil.updateModel(packageModule) { rootModel ->
-                            val contentEntry = rootModel.contentEntries.singleOrNull() ?: return@updateModel
-                            contentEntry.setup(packageContentRoot)
-                        }
+                        pkg.contentRoot?.setupContentRoots(project, ContentEntry::setup)
                     }
                 }
             }
@@ -422,6 +424,13 @@ private fun setupProjectRoots(project: Project, cargoProjects: List<CargoProject
     }
 }
 
+private fun VirtualFile.setupContentRoots(project: Project, setup: ContentEntry.(VirtualFile) -> Unit) {
+    val packageModule = ModuleUtilCore.findModuleForFile(this, project) ?: return
+    ModuleRootModificationUtil.updateModel(packageModule) { rootModel ->
+        rootModel.contentEntries.singleOrNull()?.setup(this)
+    }
+}
+
 private fun fetchStdlib(
     project: Project,
     queue: BackgroundTaskQueue,
diff --git a/src/test/kotlin/org/rustSlowTests/RsContentRootsTest.kt b/src/test/kotlin/org/rustSlowTests/RsContentRootsTest.kt
index b203d2519..88efe8935 100644
--- a/src/test/kotlin/org/rustSlowTests/RsContentRootsTest.kt
+++ b/src/test/kotlin/org/rustSlowTests/RsContentRootsTest.kt
@@ -12,7 +12,7 @@ import org.rust.cargo.RsWithToolchainTestBase
 
 class RsContentRootsTest : RsWithToolchainTestBase() {
 
-    fun test() {
+    fun `test project with subproject`() {
         val project = buildProject {
             toml("Cargo.toml", """
                 [package]
@@ -64,6 +64,66 @@ class RsContentRootsTest : RsWithToolchainTestBase() {
         check(projectFolders)
     }
 
+    fun `test workspace without root package`() {
+        val project = buildProject {
+            toml("Cargo.toml", """
+                [workspace]
+                members = ["package1", "package2"]
+            """)
+            dir("target") {}
+
+            dir("package1") {
+                toml("Cargo.toml", """
+                    [package]
+                    name = "package1"
+                    version = "0.1.0"
+                    authors = []
+                """)
+                dir("src") {
+                    rust("main.rs", "")
+                }
+                dir("examples") {}
+                dir("tests") {}
+                dir("benches") {}
+                dir("target") {}
+            }
+            dir("package2") {
+                toml("Cargo.toml", """
+                    [package]
+                    name = "package2"
+                    version = "0.1.0"
+                    authors = []
+                """)
+                dir("src") {
+                    rust("main.rs", "")
+                }
+                dir("examples") {}
+                dir("tests") {}
+                dir("benches") {}
+                dir("target") {}
+            }
+        }
+
+        val projectFolders = listOf(
+            ProjectFolder.Excluded(project.findFile("target")),
+            
+            ProjectFolder.Source(project.findFile("package1/src"), false),
+            ProjectFolder.Source(project.findFile("package1/examples"), false),
+            ProjectFolder.Source(project.findFile("package1/tests"), true),
+            ProjectFolder.Source(project.findFile("package1/benches"), true),
+            ProjectFolder.Excluded(project.findFile("package1/target")),
+
+            ProjectFolder.Source(project.findFile("package2/src"), false),
+            ProjectFolder.Source(project.findFile("package2/examples"), false),
+            ProjectFolder.Source(project.findFile("package2/tests"), true),
+            ProjectFolder.Source(project.findFile("package2/benches"), true),
+            ProjectFolder.Excluded(project.findFile("package2/target"))
+        )
+
+        check(projectFolders)
+    }
+
+
     private fun check(projectFolders: List<ProjectFolder>) {
         ModuleRootModificationUtil.updateModel(myModule) { model ->
             val contentEntry = model.contentEntries.firstOrNull() ?: error("")