Skip to content

Commit d205199

Browse files
Merge pull request #172 from mezpahlan/windows_support
Experimental Windows support - take 2
2 parents c5143b2 + b2acbc4 commit d205199

File tree

10 files changed

+184
-80
lines changed

10 files changed

+184
-80
lines changed

affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleConfiguration.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.dropbox.affectedmoduledetector
22

3+
import com.dropbox.affectedmoduledetector.util.toOsSpecificPath
34
import java.io.File
45

56
class AffectedModuleConfiguration {
@@ -41,7 +42,7 @@ class AffectedModuleConfiguration {
4142
* @see CustomTask - Implementation class
4243
* @see AffectedModuleDetectorPlugin - gradle plugin
4344
*/
44-
var customTasks = emptySet<AffectedModuleConfiguration.CustomTask>()
45+
var customTasks = emptySet<CustomTask>()
4546

4647
/**
4748
* Folder to place the log in
@@ -66,7 +67,8 @@ class AffectedModuleConfiguration {
6667
requireNotNull(baseDir) {
6768
"baseDir must be set to use pathsAffectingAllModules"
6869
}
69-
field = value
70+
// Protect against users specifying the wrong path separator for their OS.
71+
field = value.map { it.toOsSpecificPath() }.toSet()
7072
}
7173
get() {
7274
field.forEach { path ->

affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetector.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.dropbox.affectedmoduledetector.AffectedModuleDetector.Companion.DEPEN
2626
import com.dropbox.affectedmoduledetector.AffectedModuleDetector.Companion.ENABLE_ARG
2727
import com.dropbox.affectedmoduledetector.AffectedModuleDetector.Companion.MODULES_ARG
2828
import com.dropbox.affectedmoduledetector.commitshaproviders.CommitShaProvider
29+
import com.dropbox.affectedmoduledetector.util.toPathSections
2930
import org.gradle.api.GradleException
3031
import org.gradle.api.Project
3132
import org.gradle.api.Task
@@ -495,9 +496,13 @@ class AffectedModuleDetectorImpl constructor(
495496
}
496497
}
497498

498-
private fun affectsAllModules(file: String): Boolean {
499+
private fun affectsAllModules(relativeFilePath: String): Boolean {
499500
logger?.info("Paths affecting all modules: ${config.pathsAffectingAllModules}")
500-
return config.pathsAffectingAllModules.any { file.startsWith(it) }
501+
502+
val pathSections = relativeFilePath.toPathSections(rootProject.projectDir, git.getGitRoot())
503+
val projectRelativePath = pathSections.joinToString(File.separatorChar.toString())
504+
505+
return config.pathsAffectingAllModules.any { projectRelativePath.startsWith(it) }
501506
}
502507

503508
private fun findContainingProject(filePath: String): Project? {

affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/GitClient.kt

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,18 @@
2121
package com.dropbox.affectedmoduledetector
2222

2323
import com.dropbox.affectedmoduledetector.commitshaproviders.CommitShaProvider
24+
import com.dropbox.affectedmoduledetector.util.toOsSpecificLineEnding
25+
import com.dropbox.affectedmoduledetector.util.toOsSpecificPath
26+
import org.gradle.api.logging.Logger
2427
import java.io.File
2528
import java.util.concurrent.TimeUnit
26-
import org.gradle.api.logging.Logger
2729

2830
interface GitClient {
2931
fun findChangedFiles(
3032
top: Sha = "HEAD",
3133
includeUncommitted: Boolean = false
3234
): List<String>
35+
3336
fun getGitRoot(): File
3437

3538
/**
@@ -40,10 +43,12 @@ interface GitClient {
4043
* Executes the given shell command and returns the stdout as a string.
4144
*/
4245
fun execute(command: String): String
46+
4347
/**
4448
* Executes the given shell command and returns the stdout by lines.
4549
*/
4650
fun executeAndParse(command: String): List<String>
51+
4752
/**
4853
* Executes the given shell command and returns the first stdout line.
4954
*/
@@ -80,11 +85,13 @@ internal class GitClientImpl(
8085
val sha = commitShaProvider.get(commandRunner)
8186

8287
// use this if we don't want local changes
83-
return commandRunner.executeAndParse(if (includeUncommitted) {
84-
"$CHANGED_FILES_CMD_PREFIX $sha"
85-
} else {
86-
"$CHANGED_FILES_CMD_PREFIX $top..$sha"
87-
})
88+
return commandRunner.executeAndParse(
89+
if (includeUncommitted) {
90+
"$CHANGED_FILES_CMD_PREFIX $sha"
91+
} else {
92+
"$CHANGED_FILES_CMD_PREFIX $top..$sha"
93+
}
94+
)
8895
}
8996

9097
private fun findGitDirInParentFilepath(filepath: File): File? {
@@ -97,6 +104,7 @@ internal class GitClientImpl(
97104
}
98105
return null
99106
}
107+
100108
@Suppress("LongParameterList")
101109
private fun parseCommitLogString(
102110
commitLogString: String,
@@ -162,13 +170,12 @@ internal class GitClientImpl(
162170
check(proc.exitValue() == 0) { "Nonzero exit value running git command." }
163171
return stdout
164172
}
173+
165174
override fun executeAndParse(command: String): List<String> {
166-
val response = execute(command)
175+
return execute(command).toOsSpecificLineEnding()
167176
.split(System.lineSeparator())
168-
.filterNot {
169-
it.isEmpty()
170-
}
171-
return response
177+
.map { it.toOsSpecificPath() }
178+
.filterNot { it.isEmpty() }
172179
}
173180

174181
override fun executeAndParseFirst(command: String): String {

affectedmoduledetector/src/main/kotlin/com/dropbox/affectedmoduledetector/ProjectGraph.kt

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
1-
/*
2-
* Copyright 2018 The Android Open Source Project
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
1+
/*
2+
* Copyright 2018 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
1616

17-
/*
18-
* Copyright (c) 2020, Dropbox, Inc. All rights reserved.
19-
*/
17+
/*
18+
* Copyright (c) 2020, Dropbox, Inc. All rights reserved.
19+
*/
2020

2121
package com.dropbox.affectedmoduledetector
2222

23+
import com.dropbox.affectedmoduledetector.util.toPathSections
2324
import org.gradle.api.Project
2425
import org.gradle.api.logging.Logger
2526
import java.io.File
2627

27-
/**
28+
/**
2829
* Creates a project graph for fast lookup by file path
2930
*/
3031
internal class ProjectGraph(project: Project, val gitRoot: File, val logger: Logger? = null) {
@@ -52,24 +53,11 @@ internal class ProjectGraph(project: Project, val gitRoot: File, val logger: Log
5253
* Finds the project that contains the given file.
5354
* The file's path prefix should match the project's path.
5455
*/
55-
fun findContainingProject(filePath: String): Project? {
56-
val sections = filePath.split(File.separatorChar)
57-
val realSections = sections.toMutableList()
58-
val projectRelativeDir = findProjectRelativeDir()
59-
for (dir in projectRelativeDir) {
60-
if (realSections.isNotEmpty() && dir == realSections.first()) {
61-
realSections.removeAt(0)
62-
} else {
63-
break
64-
}
65-
}
66-
67-
logger?.info("finding containing project for $filePath , sections: $realSections")
68-
return rootNode.find(realSections, 0)
69-
}
56+
fun findContainingProject(relativeFilePath: String): Project? {
57+
val pathSections = relativeFilePath.toPathSections(rootProjectDir, gitRoot)
7058

71-
private fun findProjectRelativeDir(): List<String> {
72-
return rootProjectDir.toRelativeString(gitRoot).split(File.separatorChar)
59+
logger?.info("finding containing project for $relativeFilePath , sections: $pathSections")
60+
return rootNode.find(pathSections, 0)
7361
}
7462

7563
private class Node(val logger: Logger? = null) {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.dropbox.affectedmoduledetector.util
2+
3+
import java.io.File
4+
5+
/**
6+
* Converts a [String] representation of a relative [File] path to sections based on the OS
7+
* specific separator character.
8+
*/
9+
fun String.toPathSections(rootProjectDir: File, gitRootDir: File): List<String> {
10+
val realSections = toOsSpecificPath()
11+
.split(File.separatorChar)
12+
.toMutableList()
13+
val projectRelativeDirectorySections = rootProjectDir
14+
.toRelativeString(gitRootDir)
15+
.split(File.separatorChar)
16+
for (directorySection in projectRelativeDirectorySections) {
17+
if (realSections.isNotEmpty() && realSections.first() == directorySection) {
18+
realSections.removeAt(0)
19+
} else {
20+
break
21+
}
22+
}
23+
return realSections.toList()
24+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.dropbox.affectedmoduledetector.util
2+
3+
import java.io.File
4+
5+
/**
6+
* Returns an OS specific path respecting the separator character for the operating system.
7+
*
8+
* The Git client appears to only talk Unix-like paths however the Gradle client understands all
9+
* OS path variations. This causes issues on systems other than those that use the "/" path
10+
* character i.e. Windows. Therefore we need to normalise the path.
11+
*/
12+
fun String.toOsSpecificPath(): String {
13+
return this.split("/").joinToString(File.separator)
14+
}
15+
16+
/**
17+
* Returns a String with an OS specific line endings for the operating system.
18+
*
19+
* The Git client appears to only talk Unix-like line endings ("\n") however the Gradle client
20+
* understands all OS line ending variants. This causes issues on systems other than those that
21+
* use Unix-like line endings i.e. Windows. Therefore we need to normalise the line endings.
22+
*/
23+
fun String.toOsSpecificLineEnding(): String {
24+
return this.replace("\n", System.lineSeparator())
25+
}

affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleConfigurationTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class AffectedModuleConfigurationTest {
4545
@Test
4646
fun `GIVEN AffectedModuleConfiguration WHEN log folder is set THEN log folder is set`() {
4747
// GIVEN
48-
val sample = "sammple"
48+
val sample = "sample"
4949

5050
// WHEN
5151
config.logFolder = sample

affectedmoduledetector/src/test/kotlin/com/dropbox/affectedmoduledetector/AffectedModuleDetectorImplTest.kt

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ class AffectedModuleDetectorImplTest {
2121
@JvmField
2222
val attachLogsRule = AttachLogsTestRule()
2323
private val logger = attachLogsRule.logger
24+
2425
@Rule
2526
@JvmField
2627
val tmpFolder = TemporaryFolder()
28+
2729
@Rule
2830
@JvmField
2931
val tmpFolder2 = TemporaryFolder()
@@ -43,10 +45,10 @@ class AffectedModuleDetectorImplTest {
4345
private lateinit var p12: Project
4446
private lateinit var p13: Project
4547
private val pathsAffectingAllModules = setOf(
46-
"tools/android/buildSrc",
47-
"android/gradlew",
48-
"android/gradle",
49-
"dbx/core/api/"
48+
convertToFilePath("tools", "android", "buildSrc"),
49+
convertToFilePath("android", "gradlew"),
50+
convertToFilePath("android", "gradle"),
51+
convertToFilePath("dbx", "core", "api")
5052
)
5153
private lateinit var affectedModuleConfiguration: AffectedModuleConfiguration
5254

@@ -81,18 +83,21 @@ class AffectedModuleDetectorImplTest {
8183
8284
*/
8385

86+
// Root projects
8487
root = ProjectBuilder.builder()
8588
.withProjectDir(tmpDir)
8689
.withName("root")
8790
.build()
8891
// Project Graph expects supportRootFolder.
89-
(root.properties.get("ext") as ExtraPropertiesExtension).set("supportRootFolder", tmpDir)
92+
(root.properties["ext"] as ExtraPropertiesExtension).set("supportRootFolder", tmpDir)
9093
root2 = ProjectBuilder.builder()
9194
.withProjectDir(tmpDir2)
9295
.withName("root2/ui")
9396
.build()
9497
// Project Graph expects supportRootFolder.
95-
(root2.properties.get("ext") as ExtraPropertiesExtension).set("supportRootFolder", tmpDir2)
98+
(root2.properties["ext"] as ExtraPropertiesExtension).set("supportRootFolder", tmpDir2)
99+
100+
// Library modules
96101
p1 = ProjectBuilder.builder()
97102
.withProjectDir(tmpDir.resolve("p1"))
98103
.withName("p1")
@@ -104,29 +109,29 @@ class AffectedModuleDetectorImplTest {
104109
.withParent(root)
105110
.build()
106111
p3 = ProjectBuilder.builder()
107-
.withProjectDir(tmpDir.resolve("p1:p3"))
112+
.withProjectDir(tmpDir.resolve("p1/p3"))
108113
.withName("p3")
109114
.withParent(p1)
110115
.build()
111116
val p3config = p3.configurations.create("p3config")
112117
p3config.dependencies.add(p3.dependencies.project(mutableMapOf("path" to ":p1")))
113118
p4 = ProjectBuilder.builder()
114-
.withProjectDir(tmpDir.resolve("p1:p3:p4"))
119+
.withProjectDir(tmpDir.resolve("p1/p3/p4"))
115120
.withName("p4")
116121
.withParent(p3)
117122
.build()
118123
val p4config = p4.configurations.create("p4config")
119124
p4config.dependencies.add(p4.dependencies.project(mutableMapOf("path" to ":p1:p3")))
120125
p5 = ProjectBuilder.builder()
121-
.withProjectDir(tmpDir.resolve("p2:p5"))
126+
.withProjectDir(tmpDir.resolve("p2/p5"))
122127
.withName("p5")
123128
.withParent(p2)
124129
.build()
125130
val p5config = p5.configurations.create("p5config")
126131
p5config.dependencies.add(p5.dependencies.project(mutableMapOf("path" to ":p2")))
127132
p5config.dependencies.add(p5.dependencies.project(mutableMapOf("path" to ":p1:p3")))
128133
p6 = ProjectBuilder.builder()
129-
.withProjectDir(tmpDir.resolve("p1:p3:p6"))
134+
.withProjectDir(tmpDir.resolve("p1/p3/p6"))
130135
.withName("p6")
131136
.withParent(p3)
132137
.build()
@@ -152,6 +157,8 @@ class AffectedModuleDetectorImplTest {
152157
.withName("benchmark")
153158
.withParent(root)
154159
.build()
160+
161+
// UI modules
155162
p12 = ProjectBuilder.builder()
156163
.withProjectDir(tmpDir2.resolve("compose"))
157164
.withName("compose")
@@ -630,6 +637,7 @@ class AffectedModuleDetectorImplTest {
630637
)
631638
)
632639
}
640+
633641
@Test
634642
fun changeInNormalOnlyDependent_normalBuild() {
635643
val detector = AffectedModuleDetectorImpl(

0 commit comments

Comments
 (0)