Skip to content

Commit 078ed37

Browse files
authored
build: resolve cmake executable at configuration time (#319)
* build: resolve cmake executable at configuration time GUI-launched IDEs on macOS don't inherit Homebrew's /opt/homebrew/bin PATH, so the configure/compile Exec tasks invoking `commandLine("cmake", ...)` can fail with "Cannot run program 'cmake'" even when cmake is installed. Resolve an absolute cmake path at configuration time via CmakeDetection: check $CMAKE env var, scan $PATH, then fall back to common install paths for macOS/Linux/Windows. * build: require isFile in cmake detection and normalize to absolute path Address Copilot review nits on #319: - `File.canExecute()` alone returns true for directories with +x on POSIX, so a directory named `cmake` under a PATH entry (or in common install locations) could be incorrectly selected. Require `isFile && canExecute()`. - A relative `$CMAKE` value (e.g. `./cmake-wrapper`) was returned as-is, contradicting the "absolute path" KDoc. Normalize all detected candidates via `absolutePath`.
1 parent 56f912d commit 078ed37

2 files changed

Lines changed: 51 additions & 2 deletions

File tree

buildSrc/src/main/kotlin/multik.cmake-build.gradle.kts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import org.jetbrains.kotlinx.multik.builds.CmakeDetection
12
import org.jetbrains.kotlinx.multik.builds.HomebrewGccDetection
23
import org.jetbrains.kotlinx.multik.builds.HostDetection
34

45
val cmakePath = "${rootDir}/multik-openblas/multik_jni"
56
val cmakeBuildDir = layout.buildDirectory.dir("cmake-build").map { it.asFile.absolutePath }
67

8+
val cmakeExecutable = CmakeDetection.executable
79
val cmakeCCompiler = System.getenv("CMAKE_C_COMPILER")
810
?: HomebrewGccDetection.cCompiler
911
?: "gcc"
@@ -26,7 +28,7 @@ val configureCmake by tasks.registering(Exec::class) {
2628
dependsOn(createBuildDir)
2729

2830
val args = mutableListOf(
29-
"cmake",
31+
cmakeExecutable,
3032
"-DCMAKE_BUILD_TYPE=Release",
3133
"-DCMAKE_C_COMPILER=$cmakeCCompiler",
3234
"-DCMAKE_CXX_COMPILER=$cmakeCxxCompiler",
@@ -46,7 +48,7 @@ val compileCmake by tasks.registering(Exec::class) {
4648
dependsOn(configureCmake)
4749

4850
val processors = Runtime.getRuntime().availableProcessors().toString()
49-
commandLine("cmake", "--build", cmakeBuildDir.get(), "--target", "multik_jni-$targetOS", "--", "-j", processors)
51+
commandLine(cmakeExecutable, "--build", cmakeBuildDir.get(), "--target", "multik_jni-$targetOS", "--", "-j", processors)
5052
}
5153

5254
val copyNativeLibs by tasks.registering {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.jetbrains.kotlinx.multik.builds
2+
3+
import java.io.File
4+
5+
/**
6+
* Resolves an absolute path to the `cmake` executable.
7+
*
8+
* The `cmake` Gradle tasks run as `Exec` and inherit the PATH of whatever process launched Gradle.
9+
* GUI-launched IDEs on macOS don't pick up Homebrew's `/opt/homebrew/bin`, so `commandLine("cmake", ...)`
10+
* can fail with "Cannot run program 'cmake'" even when the binary is installed. Resolving to an
11+
* absolute path at configuration time sidesteps PATH inheritance entirely.
12+
*/
13+
object CmakeDetection {
14+
15+
/** Absolute path to `cmake`, or plain `"cmake"` as a last resort. */
16+
val executable: String by lazy {
17+
System.getenv("CMAKE")?.let(::File)?.takeIf { it.isFile && it.canExecute() }?.absolutePath
18+
?: findOnPath()
19+
?: commonInstallPaths.map(::File).firstOrNull { it.isFile && it.canExecute() }?.absolutePath
20+
?: "cmake"
21+
}
22+
23+
private val commonInstallPaths: List<String> = when {
24+
HostDetection.isMacosArm64 -> listOf("/opt/homebrew/bin/cmake", "/usr/local/bin/cmake")
25+
HostDetection.isMacosX64 -> listOf("/usr/local/bin/cmake", "/opt/homebrew/bin/cmake")
26+
HostDetection.isLinux -> listOf("/usr/bin/cmake", "/usr/local/bin/cmake")
27+
HostDetection.isWindows -> listOf(
28+
"C:\\Program Files\\CMake\\bin\\cmake.exe",
29+
"C:\\Program Files (x86)\\CMake\\bin\\cmake.exe"
30+
)
31+
else -> emptyList()
32+
}
33+
34+
private fun findOnPath(): String? {
35+
val path = System.getenv("PATH") ?: return null
36+
val separator = File.pathSeparator
37+
val exeNames = if (HostDetection.isWindows) listOf("cmake.exe", "cmake") else listOf("cmake")
38+
for (dir in path.split(separator)) {
39+
if (dir.isEmpty()) continue
40+
for (name in exeNames) {
41+
val candidate = File(dir, name)
42+
if (candidate.isFile && candidate.canExecute()) return candidate.absolutePath
43+
}
44+
}
45+
return null
46+
}
47+
}

0 commit comments

Comments
 (0)