Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package com.facebook.react.tasks.internal

import com.facebook.react.utils.Os.unixifyPath
import com.facebook.react.utils.findGitBashOnWindows
import com.facebook.react.utils.windowsAwareBashCommandLine
import java.io.FileOutputStream
import org.gradle.api.file.DirectoryProperty
Expand Down Expand Up @@ -53,7 +54,7 @@ abstract class BuildCodegenCLITask : Exec() {
commandLine(
windowsAwareBashCommandLine(
codegenDir.asFile.get().canonicalPath.unixifyPath().plus(BUILD_SCRIPT_PATH),
bashWindowsHome = bashWindowsHome.orNull,
bashWindowsHome = bashWindowsHome.orNull ?: findGitBashOnWindows(),
),
)
super.exec()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

package com.facebook.react.utils

import java.io.File

fun windowsAwareCommandLine(vararg args: Any): List<Any> = windowsAwareCommandLine(args.toList())

fun windowsAwareCommandLine(args: List<Any>): List<Any> =
Expand All @@ -25,3 +27,27 @@ fun windowsAwareBashCommandLine(
} else {
args.toList()
}

/**
* Returns `<root>\bin\bash.exe` for the first `<root>\<folder>\git.exe` on the PATH that has one,
* or null. This is where Git for Windows keeps its bash.
*
* A bare `bash` is not reliable on Windows: the Git installer adds only `<git>\cmd` to the PATH by
* default, and when WSL is installed `bash` resolves to the WSL launcher in `System32`, which
* Windows searches before the PATH.
*/
fun findGitBashOnWindows(path: String? = System.getenv("PATH")): String? {
if (!Os.isWindows() || path == null) {
return null
}
return path
.split(File.pathSeparatorChar)
.asSequence()
.map { it.trim().removeSurrounding("\"") }
.filter { it.isNotEmpty() }
.map { File(it, "git.exe") }
.filter { it.isFile }
.mapNotNull { it.absoluteFile.parentFile?.parentFile?.resolve("bin/bash.exe") }
.firstOrNull { it.isFile }
?.absolutePath
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ package com.facebook.react.utils
import com.facebook.react.tests.OS
import com.facebook.react.tests.OsRule
import com.facebook.react.tests.WithOs
import java.io.File
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class TaskUtilsTest {

@get:Rule val osRule = OsRule()

@get:Rule val tempFolder = TemporaryFolder()

@Test
fun windowsAwareCommandLine_withEmptyInput_isEmpty() {
assertThat(windowsAwareCommandLine().isEmpty()).isTrue()
Expand Down Expand Up @@ -73,4 +77,66 @@ class TaskUtilsTest {
assertThat(listOf("/custom/bash", "-c", "a", "b", "c"))
.isEqualTo(windowsAwareBashCommandLine("a", "b", "c", bashWindowsHome = "/custom/bash"))
}

@Test
@WithOs(OS.WIN)
fun findGitBashOnWindows_withGitOnPath_returnsBashOfThatInstall() {
val gitRoot = createGitInstall("Git")

assertThat(findGitBashOnWindows(File(gitRoot, "cmd").absolutePath))
.isEqualTo(File(gitRoot, "bin/bash.exe").absolutePath)
}

@Test
@WithOs(OS.WIN)
fun findGitBashOnWindows_skipsGitWithoutBashNextToIt() {
val gitWithoutBash = tempFolder.newFolder("mingw64", "bin")
File(gitWithoutBash, "git.exe").createNewFile()
val gitRoot = createGitInstall("Git")
val path =
listOf(gitWithoutBash.absolutePath, File(gitRoot, "cmd").absolutePath)
.joinToString(File.pathSeparator)

assertThat(findGitBashOnWindows(path)).isEqualTo(File(gitRoot, "bin/bash.exe").absolutePath)
}

@Test
@WithOs(OS.WIN)
fun findGitBashOnWindows_withoutGitOnPath_returnsNull() {
assertThat(findGitBashOnWindows(tempFolder.newFolder("empty").absolutePath)).isNull()
}

@Test
@WithOs(OS.WIN)
fun findGitBashOnWindows_withBashButNoGitOnPath_returnsNull() {
val toolsRoot = tempFolder.newFolder("tools")
File(toolsRoot, "cmd").mkdirs()
File(toolsRoot, "bin").mkdirs()
File(toolsRoot, "bin/bash.exe").createNewFile()

assertThat(findGitBashOnWindows(File(toolsRoot, "cmd").absolutePath)).isNull()
}

@Test
@WithOs(OS.WIN)
fun findGitBashOnWindows_withNullPath_returnsNull() {
assertThat(findGitBashOnWindows(null)).isNull()
}

@Test
@WithOs(OS.LINUX)
fun findGitBashOnWindows_onLinux_returnsNull() {
val gitRoot = createGitInstall("Git")

assertThat(findGitBashOnWindows(File(gitRoot, "cmd").absolutePath)).isNull()
}

private fun createGitInstall(name: String): File {
val gitRoot = tempFolder.newFolder(name)
File(gitRoot, "cmd").mkdirs()
File(gitRoot, "cmd/git.exe").createNewFile()
File(gitRoot, "bin").mkdirs()
File(gitRoot, "bin/bash.exe").createNewFile()
return gitRoot
}
}
2 changes: 1 addition & 1 deletion packages/react-native-codegen/scripts/oss/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ else
# during recursive copying, and `rsync` is not installed by default in Git Bash.
# As an added benefit, blob copy is faster.
if [ "$OSTYPE" = "msys" ] || [ "$OSTYPE" = "cygwin" ]; then
tar cf - --exclude='*.lock' "$CODEGEN_DIR" | (cd "$TMP_DIR" && tar xvf - );
tar cf - --exclude='*.lock' -C "$CODEGEN_DIR" . | (cd "$TMP_DIR" && tar xvf - );
else
/bin/cp -R "$CODEGEN_DIR/." "$TMP_DIR";
fi
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native/ReactAndroid/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ val prepareKotlinBuildScriptModel by
val buildCodegenCLI by
tasks.registering(BuildCodegenCLITask::class) {
codegenDir.set(file("$rootDir/node_modules/@react-native/codegen"))
bashWindowsHome.set(project.findProperty("react.internal.windowsBashPath").toString())
bashWindowsHome.set(project.findProperty("react.internal.windowsBashPath")?.toString())
logFile.set(file("$buildDir/codegen.log"))
inputFiles.set(fileTree(codegenDir) { include("src/**/*.js") })
outputFiles.set(
Expand Down
Loading