Skip to content
Closed
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
@@ -0,0 +1,116 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.tasks.internal

import javax.inject.Inject
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.CopySpec
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*

/**
* A task that takes care of extracting gflags from a source folder/zip and preparing it to be
* consumed by the NDK. This task will also take care of applying the mapping for gflags parameters.
*/
abstract class PrepareGflagsTask : DefaultTask() {

@get:InputFiles abstract val gflagsPath: ConfigurableFileCollection
@get:InputDirectory abstract val gflagsThirdPartyPath: DirectoryProperty
@get:Input abstract val gflagsVersion: Property<String>

@get:OutputDirectory abstract val outputDir: DirectoryProperty

@get:Inject abstract val fs: FileSystemOperations

@TaskAction
fun taskAction() {
val commonCopyConfig: (action: CopySpec) -> Unit = { action ->
action.from(gflagsPath)
action.from(gflagsThirdPartyPath)
action.duplicatesStrategy = DuplicatesStrategy.INCLUDE
action.includeEmptyDirs = false
action.into(outputDir)
}

fs.copy { action ->
commonCopyConfig(action)
action.include(
"gflags-${gflagsVersion.get()}/src/*.h",
"gflags-${gflagsVersion.get()}/src/*.cc",
"CMakeLists.txt")
action.filesMatching("*/src/*") { matchedFile ->
matchedFile.path = "gflags/${matchedFile.name}"
}
}

fs.copy { action ->
commonCopyConfig(action)
action.include("gflags-${gflagsVersion.get()}/src/gflags_declare.h.in")
action.filesMatching("*/src/*") { matchedFile ->
matchedFile.filter { line ->
// Replace all placeholders with appropriate values
// see https://github.com/gflags/gflags/blob/v2.2.0/src/gflags_declare.h.in
line
.replace(Regex("@GFLAGS_NAMESPACE@"), "gflags")
.replace(
Regex(
"@(HAVE_STDINT_H|HAVE_SYS_TYPES_H|HAVE_INTTYPES_H|GFLAGS_INTTYPES_FORMAT_C99)@"),
"1")
.replace(Regex("@([A-Z0-9_]+)@"), "1")
}
matchedFile.path = "gflags/${matchedFile.name.removeSuffix(".in")}"
}
}

fs.copy { action ->
commonCopyConfig(action)
action.include("gflags-${gflagsVersion.get()}/src/config.h.in")
action.filesMatching("*/src/*") { matchedFile ->
matchedFile.filter { line -> line.replace(Regex("^#cmakedefine"), "//cmakedefine") }
matchedFile.path = "gflags/${matchedFile.name.removeSuffix(".in")}"
}
}

fs.copy { action ->
commonCopyConfig(action)
action.include("gflags-${gflagsVersion.get()}/src/gflags_ns.h.in")
action.filesMatching("*/src/*") { matchedFile ->
matchedFile.filter { line ->
line.replace(Regex("@ns@"), "google").replace(Regex("@NS@"), "google".uppercase())
}
matchedFile.path = "gflags/gflags_google.h"
}
}

fs.copy { action ->
commonCopyConfig(action)
action.include("gflags-${gflagsVersion.get()}/src/gflags.h.in")
action.filesMatching("*/src/*") { matchedFile ->
matchedFile.filter { line ->
line
.replace(Regex("@GFLAGS_ATTRIBUTE_UNUSED@"), "")
.replace(Regex("@INCLUDE_GFLAGS_NS_H@"), "#include \"gflags/gflags_google.h\"")
}
matchedFile.path = "gflags/${matchedFile.name.removeSuffix(".in")}"
}
}

fs.copy { action ->
commonCopyConfig(action)
action.include("gflags-${gflagsVersion.get()}/src/gflags_completions.h.in")
action.filesMatching("*/src/*") { matchedFile ->
matchedFile.filter { line -> line.replace(Regex("@GFLAGS_NAMESPACE@"), "gflags") }
matchedFile.path = "gflags/${matchedFile.name.removeSuffix(".in")}"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.tasks.internal

import com.facebook.react.tests.createProject
import com.facebook.react.tests.createTestTask
import java.io.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class PrepareGflagsTaskTest {

@get:Rule val tempFolder = TemporaryFolder()

@Test(expected = IllegalStateException::class)
fun prepareGflagsTask_withMissingConfiguration_fails() {
val task = createTestTask<PrepareGflagsTask>()

task.taskAction()
}

@Test
fun prepareGflagsTask_copiesCMakefile() {
val gflagspath = tempFolder.newFolder("gflagspath")
val output = tempFolder.newFolder("output")
val project = createProject()
val gflagsThirdPartyPath = File(project.projectDir, "src/main/jni/third-party/gflags/")
val task =
createTestTask<PrepareGflagsTask>(project = project) {
it.gflagsPath.setFrom(gflagspath)
it.gflagsThirdPartyPath.set(gflagsThirdPartyPath)
it.gflagsVersion.set("1.0.0")
it.outputDir.set(output)
}
File(gflagsThirdPartyPath, "CMakeLists.txt").apply {
parentFile.mkdirs()
createNewFile()
}
task.taskAction()

assertThat(output.listFiles()!!.any { it.name == "CMakeLists.txt" }).isTrue()
}

@Test
fun prepareGflagsTask_copiesSourceCodeAndHeaders() {
val gflagspath = tempFolder.newFolder("gflagspath")
val gflagsThirdPartyPath = tempFolder.newFolder("gflagspath/jni")
val output = tempFolder.newFolder("output")
val task =
createTestTask<PrepareGflagsTask> {
it.gflagsPath.setFrom(gflagspath)
it.gflagsThirdPartyPath.set(gflagsThirdPartyPath)
it.gflagsVersion.set("1.0.0")
it.outputDir.set(output)
}
File(gflagspath, "gflags-1.0.0/src/gflags.cc").apply {
parentFile.mkdirs()
createNewFile()
}
File(gflagspath, "gflags-1.0.0/src/util.h").apply {
parentFile.mkdirs()
createNewFile()
}

task.taskAction()

assertThat(File(output, "gflags/gflags.cc").exists()).isTrue()
assertThat(File(output, "gflags/util.h").exists()).isTrue()
}

@Test
fun prepareGflagsTask_replacesTokenCorrectly() {
val gflagspath = tempFolder.newFolder("gflagspath")
val gflagsThirdPartyPath = tempFolder.newFolder("gflagspath/jni")
val output = tempFolder.newFolder("output")
val task =
createTestTask<PrepareGflagsTask> {
it.gflagsPath.setFrom(gflagspath)
it.gflagsThirdPartyPath.set(gflagsThirdPartyPath)
it.gflagsVersion.set("1.0.0")
it.outputDir.set(output)
}
File(gflagspath, "gflags-1.0.0/src/gflags_declare.h.in").apply {
parentFile.mkdirs()
writeText(
"""
#define GFLAGS_NAMESPACE @GFLAGS_NAMESPACE@
#include <string>
#if @HAVE_STDINT_H@
# include <stdint.h>
#elif @HAVE_SYS_TYPES_H@
# include <sys/types.h>
#elif @HAVE_INTTYPES_H@
# include <inttypes.h>
#endif


namespace GFLAGS_NAMESPACE {

#if @GFLAGS_INTTYPES_FORMAT_C99@ // C99
typedef int32_t int32;
typedef uint32_t uint32;
typedef int64_t int64;
typedef uint64_t uint64;
#elif @GFLAGS_INTTYPES_FORMAT_BSD@ // BSD
typedef int32_t int32;
typedef u_int32_t uint32;
typedef int64_t int64;
typedef u_int64_t uint64;
#elif @GFLAGS_INTTYPES_FORMAT_VC7@ // Windows
typedef __int32 int32;
typedef unsigned __int32 uint32;
typedef __int64 int64;
typedef unsigned __int64 uint64;
#else
# error Do not know how to define a 32-bit integer quantity on your system
#endif

} // namespace GFLAGS_NAMESPACE
""")
}
File(gflagspath, "gflags-1.0.0/src/config.h.in").apply {
parentFile.mkdirs()
createNewFile()
writeText("#cmakedefine")
}
File(gflagspath, "gflags-1.0.0/src/gflags_ns.h.in").apply {
parentFile.mkdirs()
createNewFile()
writeText("@ns@ @NS@")
}
File(gflagspath, "gflags-1.0.0/src/gflags.h.in").apply {
parentFile.mkdirs()
createNewFile()
writeText("@GFLAGS_ATTRIBUTE_UNUSED@\n@INCLUDE_GFLAGS_NS_H@")
}
File(gflagspath, "gflags-1.0.0/src/gflags_completions.h.in").apply {
parentFile.mkdirs()
createNewFile()
writeText("@GFLAGS_NAMESPACE@")
}

task.taskAction()

val declareFile = File(output, "gflags/gflags_declare.h")
assertThat(declareFile.exists()).isTrue()
assertEquals(
declareFile.readText(),
"""
#define GFLAGS_NAMESPACE gflags
#include <string>
#if 1
# include <stdint.h>
#elif 1
# include <sys/types.h>
#elif 1
# include <inttypes.h>
#endif


namespace GFLAGS_NAMESPACE {

#if 1 // C99
typedef int32_t int32;
typedef uint32_t uint32;
typedef int64_t int64;
typedef uint64_t uint64;
#elif 1 // BSD
typedef int32_t int32;
typedef u_int32_t uint32;
typedef int64_t int64;
typedef u_int64_t uint64;
#elif 1 // Windows
typedef __int32 int32;
typedef unsigned __int32 uint32;
typedef __int64 int64;
typedef unsigned __int64 uint64;
#else
# error Do not know how to define a 32-bit integer quantity on your system
#endif

} // namespace GFLAGS_NAMESPACE
""")

val configFile = File(output, "gflags/config.h")
assertThat(configFile.exists()).isTrue()
assertEquals(configFile.readText(), "//cmakedefine")

val nsFile = File(output, "gflags/gflags_google.h")
assertThat(nsFile.exists()).isTrue()
assertEquals(nsFile.readText(), "google GOOGLE")

val gflagsFile = File(output, "gflags/gflags.h")
assertThat(gflagsFile.exists()).isTrue()
assertEquals(gflagsFile.readText(), "\n#include \"gflags/gflags_google.h\"")

val completionsFile = File(output, "gflags/gflags_completions.h")
assertThat(completionsFile.exists()).isTrue()
assertEquals(completionsFile.readText(), "gflags")
}
}
1 change: 1 addition & 0 deletions packages/react-native/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ fastFloat="8.0.0"
fmt="11.0.2"
folly="2024.11.18.00"
glog="0.3.5"
gflags="2.2.0"

[libraries]
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
Expand Down
63 changes: 63 additions & 0 deletions private/react-native-fantom/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.facebook.react.tasks.internal.*
import com.facebook.react.tasks.internal.utils.*
import de.undercouch.gradle.tasks.download.Download

plugins {
id("com.facebook.react")
alias(libs.plugins.download)
}

val GFLAGS_VERSION = libs.versions.gflags.get()

val buildDir = project.layout.buildDirectory.get().asFile
val downloadsDir =
if (System.getenv("REACT_NATIVE_DOWNLOADS_DIR") != null) {
File(System.getenv("REACT_NATIVE_DOWNLOADS_DIR"))
} else {
File("$buildDir/downloads")
}
val thirdParty = File("$buildDir/third-party")
val reactNativeRootDir = projectDir.parent

val createNativeDepsDirectories by
tasks.registering {
downloadsDir.mkdirs()
thirdParty.mkdirs()
}

val downloadGflagsDest = File(downloadsDir, "gflags-${GFLAGS_VERSION}.tar.gz")
val downloadGflags by
tasks.registering(Download::class) {
dependsOn(createNativeDepsDirectories)
src("https://github.com/gflags/gflags/archive/v${GFLAGS_VERSION}.tar.gz")
onlyIfModified(true)
overwrite(false)
retries(5)
quiet(true)
dest(downloadGflagsDest)
}

val prepareGflags by
tasks.registering(PrepareGflagsTask::class) {
dependsOn(listOf(downloadGflags))
gflagsPath.setFrom(tarTree(downloadGflagsDest))
gflagsThirdPartyPath.set(project.file("tester/third-party/gflags/"))
gflagsVersion.set(GFLAGS_VERSION)
outputDir.set(File(thirdParty, "gflags"))
}

// Tasks used by Fantom to download the Native 3p dependencies used.
val prepareNative3pDependencies by
tasks.registering {
dependsOn(
prepareGflags,
)
}
Loading
Loading