/*
 * 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 java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*

/**
 * A task that takes care of unbundling JSC and preparing it for be consumed by the Android NDK.
 * Specifically it will unbundle shared libs, headers and will copy over the Makefile from
 * `src/main/jni/third-party/jsc/`
 */
abstract class PrepareJSCTask : DefaultTask() {

  @get:Input abstract val jscPackagePath: Property<String>

  @get:OutputDirectory abstract val outputDir: DirectoryProperty

  @TaskAction
  fun taskAction() {
    if (!jscPackagePath.isPresent || jscPackagePath.orNull == null) {
      error("Could not find the jsc-android npm package")
    }
    val jscDist = File(jscPackagePath.get(), "dist")
    if (!jscDist.exists()) {
      error("The jsc-android npm package is missing its \"dist\" directory")
    }
    val jscAAR =
        project.fileTree(jscDist).matching { it.include("**/android-jsc/**/*.aar") }.singleFile
    val soFiles = project.zipTree(jscAAR).matching { it.include("**/*.so") }
    val headerFiles = project.fileTree(jscDist).matching { it.include("**/include/*.h") }

    project.copy { it ->
      it.from(soFiles)
      it.from(headerFiles)
      it.from(project.file("src/main/jni/third-party/jsc/CMakeLists.txt"))
      it.filesMatching("**/*.h") { it.path = "JavaScriptCore/${it.name}" }
      it.includeEmptyDirs = false
      it.into(outputDir)
    }
  }
}


/*
 * 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 java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*

/**
 * A task that takes care of extracting Libevent from a source folder/zip and preparing it to be
 * consumed by the NDK.
 */
abstract class PrepareLibeventTask : DefaultTask() {

  @get:InputFiles abstract val libeventPath: ConfigurableFileCollection

  @get:Input abstract val libeventVersion: Property<String>

  @get:OutputDirectory abstract val outputDir: DirectoryProperty

  @TaskAction
  fun taskAction() {
    project.copy { it ->
      it.from(libeventPath)
      it.from(project.file("src/main/jni/third-party/libevent/"))
      it.include(
          "libevent-${libeventVersion.get()}-stable/*.c",
          "libevent-${libeventVersion.get()}-stable/*.h",
          "libevent-${libeventVersion.get()}-stable/include/**/*",
          "evconfig-private.h",
          "event-config.h",
          "CMakeLists.txt")
      it.eachFile { it.path = it.path.removePrefix("libevent-${libeventVersion.get()}-stable/") }
      it.includeEmptyDirs = false
      it.into(outputDir)
    }
    File(outputDir.asFile.get(), "event-config.h").apply {
      val destination =
          File(this.parentFile, "include/event2/event-config.h").apply { parentFile.mkdirs() }
      renameTo(destination)
    }
  }
}


/*
 * 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.tasks.internal.utils.PrefabPreprocessingEntry
import java.io.File
import javax.inject.Inject
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction

/**
 * A task that takes care of copying headers and filtering them so that can be consumed by the
 * Prefab protocol. This task handles also the header prefixes.
 *
 * It currently filters out some of the Boost headers as they're not used by React Native and are
 * resulting in bigger .aar (250Mb+).
 *
 * You should provide in input a list fo [PrefabPreprocessingEntry] that will be used by this task
 * to do the necessary copy operations.
 */
abstract class PreparePrefabHeadersTask : DefaultTask() {

  @get:Input abstract val input: ListProperty<PrefabPreprocessingEntry>

  @get:OutputDirectory abstract val outputDir: DirectoryProperty

  @get:Inject abstract val fs: FileSystemOperations

  @TaskAction
  fun taskAction() {
    input.get().forEach { (libraryName, pathToPrefixCouples) ->
      val outputFolder: RegularFile = outputDir.file(libraryName).get()
      pathToPrefixCouples.forEach { (headerPath, headerPrefix) ->
        fs.copy {
          it.from(headerPath)
          it.include("**/*.h")
          it.exclude("**/*.cpp")
          it.exclude("**/*.txt")
          // We don't want to copy all the boost headers as they are 250Mb+
          it.include("boost/config.hpp")
          it.include("boost/config/**/*.hpp")
          it.include("boost/core/*.hpp")
          it.include("boost/detail/workaround.hpp")
          it.include("boost/operators.hpp")
          it.include("boost/preprocessor/**/*.hpp")
          it.into(File(outputFolder.asFile, headerPrefix))
        }
      }
    }
  }
}


/*
 * 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.utils

import java.io.Serializable

/**
 * This data class represents an entry that can be consumed by the [PreparePrefabHeadersTask].
 *
 * @param libraryName The name of the library that you're preparing for Prefab
 * @param pathToPrefixCouples A list of pairs Path to Header prefix. You can use this list to supply
 *   a list of paths that you want to be considered for prefab. Each path can specify an header
 *   prefix that will be used by prefab to re-created the header layout.
 */
data class PrefabPreprocessingEntry(
    val libraryName: String,
    val pathToPrefixCouples: List<Pair<String, String>>,
) : Serializable {
  constructor(
      libraryName: String,
      pathToPrefixCouple: Pair<String, String>
  ) : this(libraryName, listOf(pathToPrefixCouple))
}


/*
 * 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.utils

import com.android.build.api.variant.AndroidComponentsExtension
import com.facebook.react.utils.ProjectUtils.isHermesEnabled
import com.facebook.react.utils.ProjectUtils.isNewArchEnabled
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.plugins.AppliedPlugin

@Suppress("UnstableApiUsage")
internal object AgpConfiguratorUtils {

  fun configureBuildConfigFields(project: Project) {
    val action =
        Action<AppliedPlugin> {
          project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext ->
            ext.buildFeatures.buildConfig = true
            ext.defaultConfig.buildConfigField(
                "boolean", "IS_NEW_ARCHITECTURE_ENABLED", project.isNewArchEnabled.toString())
            ext.defaultConfig.buildConfigField(
                "boolean", "IS_HERMES_ENABLED", project.isHermesEnabled.toString())
          }
        }
    project.pluginManager.withPlugin("com.android.application", action)
    project.pluginManager.withPlugin("com.android.library", action)
  }

  fun configureDevPorts(project: Project) {
    val devServerPort =
        project.properties["reactNativeDevServerPort"]?.toString() ?: DEFAULT_DEV_SERVER_PORT
    val inspectorProxyPort =
        project.properties["reactNativeInspectorProxyPort"]?.toString() ?: devServerPort

    val action =
        Action<AppliedPlugin> {
          project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext ->
            ext.defaultConfig.resValue("integer", "react_native_dev_server_port", devServerPort)
            ext.defaultConfig.resValue(
                "integer", "react_native_inspector_proxy_port", inspectorProxyPort)
          }
        }

    project.pluginManager.withPlugin("com.android.application", action)
    project.pluginManager.withPlugin("com.android.library", action)
  }
}

const val DEFAULT_DEV_SERVER_PORT = "8081"


/*
 * 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.utils

import java.util.*
import org.gradle.api.Project

internal object BackwardCompatUtils {

  fun configureBackwardCompatibilityReactMap(project: Project) {
    if (project.extensions.extraProperties.has("react")) {
      @Suppress("UNCHECKED_CAST")
      val reactMap =
          project.extensions.extraProperties.get("react") as? Map<String, Any?> ?: mapOf()
      if (reactMap.isNotEmpty()) {
        project.logger.error(
            """
          ********************************************************************************

          ERROR: Using old project.ext.react configuration.
          We identified that your project is using a old configuration block as:

          project.ext.react = [
              // ...
          ]

          You should migrate to the new configuration:

          react {
              // ...
          }
          You can find documentation inside `android/app/build.gradle` on how to use it.

          ********************************************************************************
          """
                .trimIndent())
      }
    }

    // We set an empty react[] map so if a library is reading it, they will find empty values.
    project.extensions.extraProperties.set("react", mapOf<String, String>())
  }
}


/*
 * 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.utils

import java.io.File
import java.net.URI
import java.util.*
import org.gradle.api.Project
import org.gradle.api.artifacts.repositories.MavenArtifactRepository

internal const val DEFAULT_GROUP_STRING = "com.facebook.react"

internal object DependencyUtils {

  /**
   * This method takes care of configuring the repositories{} block for both the app and all the 3rd
   * party libraries which are auto-linked.
   */
  fun configureRepositories(project: Project, reactNativeDir: File) {
    project.rootProject.allprojects { eachProject ->
      with(eachProject) {
        if (hasProperty("REACT_NATIVE_MAVEN_LOCAL_REPO")) {
          val mavenLocalRepoPath = property("REACT_NATIVE_MAVEN_LOCAL_REPO") as String
          mavenRepoFromURI(File(mavenLocalRepoPath).toURI())
        }
        // We add the snapshot for users on nightlies.
        mavenRepoFromUrl("https://oss.sonatype.org/content/repositories/snapshots/")
        repositories.mavenCentral { repo ->
          // We don't want to fetch JSC from Maven Central as there are older versions there.
          repo.content { it.excludeModule("org.webkit", "android-jsc") }
        }
        // Android JSC is installed from npm
        mavenRepoFromURI(File(reactNativeDir, "../jsc-android/dist").toURI())
        repositories.google()
        mavenRepoFromUrl("https://www.jitpack.io")
      }
    }
  }

  /**
   * This method takes care of configuring the resolution strategy for both the app and all the 3rd
   * party libraries which are auto-linked. Specifically it takes care of:
   * - Forcing the react-android/hermes-android version to the one specified in the package.json
   * - Substituting `react-native` with `react-android` and `hermes-engine` with `hermes-android`.
   */
  fun configureDependencies(
      project: Project,
      versionString: String,
      groupString: String = DEFAULT_GROUP_STRING
  ) {
    if (versionString.isBlank()) return
    project.rootProject.allprojects { eachProject ->
      eachProject.configurations.all { configuration ->
        // Here we set a dependencySubstitution for both react-native and hermes-engine as those
        // coordinates are voided due to https://github.com/facebook/react-native/issues/35210
        // This allows users to import libraries that are still using
        // implementation("com.facebook.react:react-native:+") and resolve the right dependency.
        configuration.resolutionStrategy.dependencySubstitution {
          getDependencySubstitutions(versionString, groupString).forEach { (module, dest, reason) ->
            it.substitute(it.module(module)).using(it.module(dest)).because(reason)
          }
        }
        configuration.resolutionStrategy.force(
            "${groupString}:react-android:${versionString}",
            "${groupString}:hermes-android:${versionString}",
        )
      }
    }
  }

  internal fun getDependencySubstitutions(
      versionString: String,
      groupString: String = DEFAULT_GROUP_STRING
  ): List<Triple<String, String, String>> {
    val dependencySubstitution = mutableListOf<Triple<String, String, String>>()
    dependencySubstitution.add(
        Triple(
            "com.facebook.react:react-native",
            "${groupString}:react-android:${versionString}",
            "The react-native artifact was deprecated in favor of react-android due to https://github.com/facebook/react-native/issues/35210."))
    dependencySubstitution.add(
        Triple(
            "com.facebook.react:hermes-engine",
            "${groupString}:hermes-android:${versionString}",
            "The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210."))
    if (groupString != DEFAULT_GROUP_STRING) {
      dependencySubstitution.add(
          Triple(
              "com.facebook.react:react-android",
              "${groupString}:react-android:${versionString}",
              "The react-android dependency was modified to use the correct Maven group."))
      dependencySubstitution.add(
          Triple(
              "com.facebook.react:hermes-android",
              "${groupString}:hermes-android:${versionString}",
              "The hermes-android dependency was modified to use the correct Maven group."))
    }
    return dependencySubstitution
  }

  fun readVersionAndGroupStrings(propertiesFile: File): Pair<String, String> {
    val reactAndroidProperties = Properties()
    propertiesFile.inputStream().use { reactAndroidProperties.load(it) }
    val versionStringFromFile = reactAndroidProperties["VERSION_NAME"] as? String ?: ""
    // If on a nightly, we need to fetch the -SNAPSHOT artifact from Sonatype.
    val versionString =
        if (versionStringFromFile.startsWith("0.0.0") || "-nightly-" in versionStringFromFile) {
          "$versionStringFromFile-SNAPSHOT"
        } else {
          versionStringFromFile
        }
    // Returns Maven group for repos using different group for Maven artifacts
    val groupString = reactAndroidProperties["GROUP"] as? String ?: DEFAULT_GROUP_STRING
    return Pair(versionString, groupString)
  }

  fun Project.mavenRepoFromUrl(url: String): MavenArtifactRepository =
      project.repositories.maven { it.url = URI.create(url) }

  fun Project.mavenRepoFromURI(uri: URI): MavenArtifactRepository =
      project.repositories.maven { it.url = uri }
}


/*
 * 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.utils

import java.io.File

internal fun File.moveTo(destination: File) {
  copyTo(destination, overwrite = true)
  delete()
}

internal fun File.recreateDir() {
  deleteRecursively()
  mkdirs()
}


/*
 * 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.utils

import com.android.build.api.variant.AndroidComponentsExtension
import com.facebook.react.utils.PropertyUtils.INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT
import org.gradle.api.Action
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.plugins.AppliedPlugin
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension

internal object JdkConfiguratorUtils {
  /**
   * Function that takes care of configuring the JDK toolchain for all the projects projects. As we
   * do decide the JDK version based on the AGP version that RNGP brings over, here we can safely
   * configure the toolchain to 17.
   */
  fun configureJavaToolChains(input: Project) {
    // Check at the app level if react.internal.disableJavaVersionAlignment is set.
    if (input.hasProperty(INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT)) {
      return
    }
    input.rootProject.allprojects { project ->
      // Allows every single module to set react.internal.disableJavaVersionAlignment also.
      if (project.hasProperty(INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT)) {
        return@allprojects
      }
      val action =
          Action<AppliedPlugin> {
            project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext
              ->
              ext.compileOptions.sourceCompatibility = JavaVersion.VERSION_17
              ext.compileOptions.targetCompatibility = JavaVersion.VERSION_17
            }
          }
      project.pluginManager.withPlugin("com.android.application", action)
      project.pluginManager.withPlugin("com.android.library", action)
      project.pluginManager.withPlugin("org.jetbrains.kotlin.android") {
        project.extensions.getByType(KotlinTopLevelExtension::class.java).jvmToolchain(17)
      }
      project.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
        project.extensions.getByType(KotlinTopLevelExtension::class.java).jvmToolchain(17)
      }
    }
  }
}


/*
 * 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.utils

import com.facebook.react.model.ModelPackageJson
import com.google.gson.Gson
import java.io.File

object JsonUtils {
  private val gsonConverter = Gson()

  fun fromCodegenJson(input: File): ModelPackageJson? =
      input.bufferedReader().use {
        runCatching { gsonConverter.fromJson(it, ModelPackageJson::class.java) }.getOrNull()
      }
}


/*
 * 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.utils

import java.util.Locale

object KotlinStdlibCompatUtils {
  @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
  fun String.lowercaseCompat(): String = (this as java.lang.String).toLowerCase(Locale.ROOT)

  fun String.capitalizeCompat(): String =
      if (isNotEmpty()) {
        val firstChar = this[0]
        val uppercaseChar = Character.toUpperCase(firstChar)
        val restString = this@capitalizeCompat.substring(1)
        uppercaseChar + restString
      } else {
        this
      }

  fun String.toBooleanStrictOrNullCompat(): Boolean? =
      when (this) {
        "true" -> true
        "false" -> false
        else -> null
      }
}


/*
 * 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.utils

import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.Variant
import com.facebook.react.ReactExtension
import com.facebook.react.utils.ProjectUtils.getReactNativeArchitectures
import com.facebook.react.utils.ProjectUtils.isNewArchEnabled
import java.io.File
import org.gradle.api.Project

internal object NdkConfiguratorUtils {
  @Suppress("UnstableApiUsage")
  fun configureReactNativeNdk(project: Project, extension: ReactExtension) {
    project.pluginManager.withPlugin("com.android.application") {
      project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext ->
        if (!project.isNewArchEnabled) {
          // For Old Arch, we don't need to setup the NDK
          return@finalizeDsl
        }
        // We enable prefab so users can consume .so/headers from ReactAndroid and hermes-engine
        // .aar
        ext.buildFeatures.prefab = true

        // If the user has not provided a CmakeLists.txt path, let's provide
        // the default one from the framework
        if (ext.externalNativeBuild.cmake.path == null) {
          ext.externalNativeBuild.cmake.path =
              File(
                  extension.reactNativeDir.get().asFile,
                  "ReactAndroid/cmake-utils/default-app-setup/CMakeLists.txt")
        }

        // Parameters should be provided in an additive manner (do not override what
        // the user provided, but allow for sensible defaults).
        val cmakeArgs = ext.defaultConfig.externalNativeBuild.cmake.arguments
        if (cmakeArgs.none { it.startsWith("-DPROJECT_BUILD_DIR") }) {
          cmakeArgs.add("-DPROJECT_BUILD_DIR=${project.buildDir}")
        }
        if (cmakeArgs.none { it.startsWith("-DREACT_ANDROID_DIR") }) {
          cmakeArgs.add(
              "-DREACT_ANDROID_DIR=${extension.reactNativeDir.file("ReactAndroid").get().asFile}")
        }
        if (cmakeArgs.none { it.startsWith("-DANDROID_STL") }) {
          cmakeArgs.add("-DANDROID_STL=c++_shared")
        }
        // Due to the new NDK toolchain file, the C++ flags gets overridden between compilation
        // units. This is causing some libraries to don't be compiled with -DANDROID and other
        // crucial flags. This can be revisited once we bump to NDK 25/26
        if (cmakeArgs.none { it.startsWith("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE") }) {
          cmakeArgs.add("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=ON")
        }

        val architectures = project.getReactNativeArchitectures()
        // abiFilters are split ABI are not compatible each other, so we set the abiFilters
        // only if the user hasn't enabled the split abi feature.
        if (architectures.isNotEmpty() && !ext.splits.abi.isEnable) {
          ext.defaultConfig.ndk.abiFilters.addAll(architectures)
        }
      }
    }
  }

  /**
   * This method is used to configure the .so Packaging Options for the given variant. It will make
   * sure we specify the correct .pickFirsts for all the .so files we are producing or that we're
   * aware of as some of our dependencies are pulling them in.
   */
  fun configureNewArchPackagingOptions(
      project: Project,
      variant: Variant,
  ) {
    if (!project.isNewArchEnabled) {
      // For Old Arch, we set a pickFirst only on libraries that we know are
      // clashing with our direct dependencies (FBJNI, Flipper and Hermes).
      variant.packaging.jniLibs.pickFirsts.addAll(
          listOf(
              "**/libfbjni.so",
              "**/libc++_shared.so",
          ))
    } else {
      // We set some packagingOptions { pickFirst ... } for our users for libraries we own.
      variant.packaging.jniLibs.pickFirsts.addAll(
          listOf(
              // This is the .so provided by FBJNI via prefab
              "**/libfbjni.so",
              // Those are prefab libraries we distribute via ReactAndroid
              // Due to a bug in AGP, they fire a warning on console as both the JNI
              // and the prefab .so files gets considered. See more on:
              "**/libfabricjni.so",
              "**/libfolly_runtime.so",
              "**/libglog.so",
              "**/libjsi.so",
              "**/libreact_codegen_rncore.so",
              "**/libreact_debug.so",
              "**/libreact_nativemodule_core.so",
              "**/libreact_newarchdefaults.so",
              "**/libreact_render_componentregistry.so",
              "**/libreact_render_core.so",
              "**/libreact_render_debug.so",
              "**/libreact_render_graphics.so",
              "**/libreact_render_imagemanager.so",
              "**/libreact_render_mapbuffer.so",
              "**/librrc_image.so",
              "**/librrc_legacyviewmanagerinterop.so",
              "**/librrc_view.so",
              "**/libruntimeexecutor.so",
              "**/libturbomodulejsijni.so",
              "**/libyoga.so",
              // AGP will give priority of libc++_shared coming from App modules.
              "**/libc++_shared.so",
          ))
    }
  }

  /**
   * This method is used to configure the .so Cleanup for the given variant. It takes care of
   * cleaning up the .so files that are not needed for Hermes or JSC, given a specific variant.
   */
  fun configureJsEnginePackagingOptions(
      config: ReactExtension,
      variant: Variant,
      hermesEnabled: Boolean,
  ) {
    if (config.enableSoCleanup.get()) {
      val (excludes, includes) = getPackagingOptionsForVariant(hermesEnabled)
      variant.packaging.jniLibs.excludes.addAll(excludes)
      variant.packaging.jniLibs.pickFirsts.addAll(includes)
    }
  }

  fun getPackagingOptionsForVariant(hermesEnabled: Boolean): Pair<List<String>, List<String>> {
    val excludes = mutableListOf<String>()
    val includes = mutableListOf<String>()
    if (hermesEnabled) {
      excludes.add("**/libjsc.so")
      excludes.add("**/libjscexecutor.so")
      includes.add("**/libhermes.so")
      includes.add("**/libhermes_executor.so")
    } else {
      excludes.add("**/libhermes.so")
      excludes.add("**/libhermes_executor.so")
      includes.add("**/libjsc.so")
      includes.add("**/libjscexecutor.so")
    }
    return excludes to includes
  }
}


/*
 * 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.utils

import com.facebook.react.utils.KotlinStdlibCompatUtils.lowercaseCompat
import java.io.File

internal object Os {

  fun isWindows(): Boolean =
      System.getProperty("os.name")?.lowercaseCompat()?.contains("windows") ?: false

  fun isMac(): Boolean = System.getProperty("os.name")?.lowercaseCompat()?.contains("mac") ?: false

  fun isLinuxAmd64(): Boolean {
    val osNameMatch = System.getProperty("os.name")?.lowercaseCompat()?.contains("linux") ?: false
    val archMatch = System.getProperty("os.arch")?.lowercaseCompat()?.contains("amd64") ?: false
    return osNameMatch && archMatch
  }

  fun String.unixifyPath() =
      this.replace('\\', '/').replace(":", "").let {
        if (!it.startsWith("/")) {
          "/$it"
        } else {
          it
        }
      }

  /**
   * As Gradle doesn't support well path with spaces on Windows, we need to return relative path on
   * Win. On Linux & Mac we'll default to return absolute path.
   */
  fun File.cliPath(base: File): String =
      if (isWindows()) {
        this.relativeTo(base).path
      } else {
        this.absolutePath
      }
}


/*
 * 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.
 */

@file:JvmName("PathUtils")

package com.facebook.react.utils

import com.facebook.react.ReactExtension
import com.facebook.react.model.ModelPackageJson
import com.facebook.react.utils.KotlinStdlibCompatUtils.capitalizeCompat
import com.facebook.react.utils.Os.cliPath
import java.io.File
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty

/**
 * Computes the entry file for React Native. The Algo follows this order:
 * 1. The file pointed by the ENTRY_FILE env variable, if set.
 * 2. The file provided by the `entryFile` config in the `reactApp` Gradle extension
 * 3. The `index.android.js` file, if available.
 * 4. Fallback to the `index.js` file.
 *
 * @param config The [ReactExtension] configured for this project
 */
internal fun detectedEntryFile(config: ReactExtension, envVariableOverride: String? = null): File =
    detectEntryFile(
        entryFile = config.entryFile.orNull?.asFile,
        reactRoot = config.root.get().asFile,
        envVariableOverride = envVariableOverride)

/**
 * Computes the CLI file for React Native. The Algo follows this order:
 * 1. The path provided by the `cliFile` config in the `react {}` Gradle extension
 * 2. The output of `node --print "require.resolve('react-native/cli');"` if not failing.
 * 3. The `node_modules/react-native/cli.js` file if exists
 * 4. Fails otherwise
 */
internal fun detectedCliFile(config: ReactExtension): File =
    detectCliFile(
        reactNativeRoot = config.root.get().asFile,
        preconfiguredCliFile = config.cliFile.asFile.orNull)

/**
 * Computes the `hermesc` command location. The Algo follows this order:
 * 1. The path provided by the `hermesCommand` config in the `react` Gradle extension
 * 2. The file located in `node_modules/react-native/sdks/hermes/build/bin/hermesc`. This will be
 *    used if the user is building Hermes from source.
 * 3. The file located in `node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc` where `%OS-BIN%`
 *    is substituted with the correct OS arch. This will be used if the user is using a precompiled
 *    hermes-engine package.
 * 4. Fails otherwise
 */
internal fun detectedHermesCommand(config: ReactExtension): String =
    detectOSAwareHermesCommand(config.root.get().asFile, config.hermesCommand.get())

private fun detectEntryFile(
    entryFile: File?,
    reactRoot: File,
    envVariableOverride: String? = null
): File =
    when {
      envVariableOverride != null -> File(reactRoot, envVariableOverride)
      entryFile != null -> entryFile
      File(reactRoot, "index.android.js").exists() -> File(reactRoot, "index.android.js")
      else -> File(reactRoot, "index.js")
    }

private fun detectCliFile(reactNativeRoot: File, preconfiguredCliFile: File?): File {
  // 1. preconfigured path
  if (preconfiguredCliFile != null) {
    if (preconfiguredCliFile.exists()) {
      return preconfiguredCliFile
    }
  }

  // 2. node module path
  val nodeProcess =
      Runtime.getRuntime()
          .exec(
              arrayOf("node", "--print", "require.resolve('react-native/cli');"),
              emptyArray(),
              reactNativeRoot)

  val nodeProcessOutput = nodeProcess.inputStream.use { it.bufferedReader().readText().trim() }

  if (nodeProcessOutput.isNotEmpty()) {
    val nodeModuleCliJs = File(nodeProcessOutput)
    if (nodeModuleCliJs.exists()) {
      return nodeModuleCliJs
    }
  }

  // 3. cli.js in the root folder
  val rootCliJs = File(reactNativeRoot, "node_modules/react-native/cli.js")
  if (rootCliJs.exists()) {
    return rootCliJs
  }

  error(
      """
      Couldn't determine CLI location!

      Please set `react { cliFile = file(...) }` inside your
      build.gradle to the path of the react-native cli.js file.
      This file typically resides in `node_modules/react-native/cli.js`
    """
          .trimIndent())
}

/**
 * Computes the `hermesc` command location. The Algo follows this order:
 * 1. The path provided by the `hermesCommand` config in the `react` Gradle extension
 * 2. The file located in `node_modules/react-native/sdks/hermes/build/bin/hermesc`. This will be
 *    used if the user is building Hermes from source.
 * 3. The file located in `node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc` where `%OS-BIN%`
 *    is substituted with the correct OS arch. This will be used if the user is using a precompiled
 *    hermes-engine package.
 * 4. Fails otherwise
 */
internal fun detectOSAwareHermesCommand(projectRoot: File, hermesCommand: String): String {
  // 1. If the project specifies a Hermes command, don't second guess it.
  if (hermesCommand.isNotBlank()) {
    val osSpecificHermesCommand =
        if ("%OS-BIN%" in hermesCommand) {
          hermesCommand.replace("%OS-BIN%", getHermesOSBin())
        } else {
          hermesCommand
        }
    return osSpecificHermesCommand
        // Execution on Windows fails with / as separator
        .replace('/', File.separatorChar)
  }

  // 2. If the project is building hermes-engine from source, use hermesc from there
  val builtHermesc =
      getBuiltHermescFile(projectRoot, System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR"))
  if (builtHermesc.exists()) {
    return builtHermesc.cliPath(projectRoot)
  }

  // 3. If the react-native contains a pre-built hermesc, use it.
  val prebuiltHermesPath =
      HERMESC_IN_REACT_NATIVE_DIR.plus(getHermesCBin())
          .replace("%OS-BIN%", getHermesOSBin())
          // Execution on Windows fails with / as separator
          .replace('/', File.separatorChar)

  val prebuiltHermes = File(projectRoot, prebuiltHermesPath)
  if (prebuiltHermes.exists()) {
    return prebuiltHermes.cliPath(projectRoot)
  }

  error(
      "Couldn't determine Hermesc location. " +
          "Please set `react.hermesCommand` to the path of the hermesc binary file. " +
          "node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc")
}

/**
 * Gets the location where Hermesc should be. If nothing is specified, built hermesc is assumed to
 * be inside [HERMESC_BUILT_FROM_SOURCE_DIR]. Otherwise user can specify an override with
 * [pathOverride], which is assumed to be an absolute path where Hermes source code is
 * provided/built.
 *
 * @param projectRoot The root of the Project.
 */
internal fun getBuiltHermescFile(projectRoot: File, pathOverride: String?) =
    if (!pathOverride.isNullOrBlank()) {
      File(pathOverride, "build/bin/${getHermesCBin()}")
    } else {
      File(projectRoot, HERMESC_BUILT_FROM_SOURCE_DIR.plus(getHermesCBin()))
    }

internal fun getHermesCBin() = if (Os.isWindows()) "hermesc.exe" else "hermesc"

internal fun getHermesOSBin(): String {
  if (Os.isWindows()) return "win64-bin"
  if (Os.isMac()) return "osx-bin"
  if (Os.isLinuxAmd64()) return "linux64-bin"
  error(
      "OS not recognized. Please set project.react.hermesCommand " +
          "to the path of a working Hermes compiler.")
}

internal fun projectPathToLibraryName(projectPath: String): String =
    projectPath
        .split(':', '-', '_', '.')
        .joinToString("") { token -> token.capitalizeCompat() }
        .plus("Spec")

/**
 * Function to look for the relevant `package.json`. We first look in the parent folder of this
 * Gradle module (generally the case for library projects) or we fallback to looking into the `root`
 * folder of a React Native project (generally the case for app projects).
 */
internal fun findPackageJsonFile(project: Project, rootProperty: DirectoryProperty): File? {
  val inParent = project.file("../package.json")
  if (inParent.exists()) {
    return inParent
  }

  val fromExtension = rootProperty.file("package.json").orNull?.asFile
  if (fromExtension?.exists() == true) {
    return fromExtension
  }

  return null
}

/**
 * Function to look for the `package.json` and parse it. It returns a [ModelPackageJson] if found or
 * null others.
 *
 * Please note that this function access the [DirectoryProperty] parameter and calls .get() on them,
 * so calling this during apply() of the ReactPlugin is not recommended. It should be invoked inside
 * lazy lambdas or at execution time.
 */
internal fun readPackageJsonFile(
    project: Project,
    rootProperty: DirectoryProperty
): ModelPackageJson? {
  val packageJson = findPackageJsonFile(project, rootProperty)
  return packageJson?.let { JsonUtils.fromCodegenJson(it) }
}

private const val HERMESC_IN_REACT_NATIVE_DIR = "node_modules/react-native/sdks/hermesc/%OS-BIN%/"
private const val HERMESC_BUILT_FROM_SOURCE_DIR =
    "node_modules/react-native/ReactAndroid/hermes-engine/build/hermes/bin/"


/*
 * 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.utils

import com.facebook.react.model.ModelPackageJson
import com.facebook.react.utils.KotlinStdlibCompatUtils.lowercaseCompat
import com.facebook.react.utils.KotlinStdlibCompatUtils.toBooleanStrictOrNullCompat
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty

internal object ProjectUtils {
  internal val Project.isNewArchEnabled: Boolean
    get() =
        project.hasProperty("newArchEnabled") &&
            project.property("newArchEnabled").toString().toBoolean()

  const val HERMES_FALLBACK = true

  internal val Project.isHermesEnabled: Boolean
    get() =
        if (project.hasProperty("hermesEnabled")) {
          project
              .property("hermesEnabled")
              .toString()
              .lowercaseCompat()
              .toBooleanStrictOrNullCompat()
              ?: true
        } else if (project.extensions.extraProperties.has("react")) {
          @Suppress("UNCHECKED_CAST")
          val reactMap = project.extensions.extraProperties.get("react") as? Map<String, Any?>
          when (val enableHermesKey = reactMap?.get("enableHermes")) {
            is Boolean -> enableHermesKey
            is String -> enableHermesKey.lowercaseCompat().toBooleanStrictOrNullCompat() ?: true
            else -> HERMES_FALLBACK
          }
        } else {
          HERMES_FALLBACK
        }

  internal fun Project.needsCodegenFromPackageJson(rootProperty: DirectoryProperty): Boolean {
    val parsedPackageJson = readPackageJsonFile(this, rootProperty)
    return needsCodegenFromPackageJson(parsedPackageJson)
  }

  internal fun Project.needsCodegenFromPackageJson(model: ModelPackageJson?): Boolean {
    return model?.codegenConfig != null
  }

  internal fun Project.getReactNativeArchitectures(): List<String> {
    val architectures = mutableListOf<String>()
    if (project.hasProperty("reactNativeArchitectures")) {
      val architecturesString = project.property("reactNativeArchitectures").toString()
      architectures.addAll(architecturesString.split(",").filter { it.isNotBlank() })
    }
    return architectures
  }
}


/*
 * 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.utils

/** Collection of all the Gradle Propreties that are accepted by React Native Gradle Plugin. */
object PropertyUtils {

  /**
   * Internal Property that acts as a killswitch to configure the JDK version and align it for app
   * and all the libraries.
   */
  const val INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT = "react.internal.disableJavaVersionAlignment"
}


/*
 * 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.utils

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

internal fun windowsAwareCommandLine(args: List<Any>): List<Any> =
    if (Os.isWindows()) {
      listOf("cmd", "/c") + args
    } else {
      args
    }

internal fun windowsAwareBashCommandLine(
    vararg args: String,
    bashWindowsHome: String? = null
): List<String> =
    if (Os.isWindows()) {
      listOf(bashWindowsHome ?: "bash", "-c") + args
    } else {
      args.toList()
    }


/*
 * 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

import org.gradle.api.Project

class TestReactExtension(project: Project) : ReactExtension(project)


/*
 * 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

import com.facebook.react.tests.createTestTask
import java.io.File
import org.gradle.api.tasks.*
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class BuildCodegenCLITaskTest {

  @get:Rule val tempFolder = TemporaryFolder()

  @Test
  fun buildCodegenCli_input_isSetCorrectly() {
    val task = createTestTask<BuildCodegenCLITask> { it.codegenDir.set(tempFolder.root) }

    assertTrue(task.input.contains(File(tempFolder.root, "scripts")))
    assertTrue(task.input.contains(File(tempFolder.root, "src")))
    assertTrue(task.input.contains(File(tempFolder.root, "package.json")))
    assertTrue(task.input.contains(File(tempFolder.root, ".babelrc")))
    assertTrue(task.input.contains(File(tempFolder.root, ".prettierrc")))
  }

  @Test
  fun buildCodegenCli_output_isSetCorrectly() {
    val task = createTestTask<BuildCodegenCLITask> { it.codegenDir.set(tempFolder.root) }

    assertTrue(task.output.contains(File(tempFolder.root, "lib")))
    assertTrue(task.output.contains(File(tempFolder.root, "node_modules")))
  }

  @Test
  fun buildCodegenCli_bashWindowsHome_isSetCorrectly() {
    val bashPath = tempFolder.newFile("bash").absolutePath
    val task = createTestTask<BuildCodegenCLITask> { it.bashWindowsHome.set(bashPath) }

    assertEquals(bashPath, task.bashWindowsHome.get())
  }

  @Test
  fun buildCodegenCli_onlyIf_withMissingDirectory_isSatisfied() {
    File(tempFolder.root, "lib/cli/").apply { mkdirs() }
    val task = createTestTask<BuildCodegenCLITask> { it.codegenDir.set(tempFolder.root) }

    assertTrue(task.onlyIf.isSatisfiedBy(task))
  }

  @Test
  fun buildCodegenCli_onlyIf_withEmptyDirectory_isSatisfied() {
    File(tempFolder.root, "lib/cli/").apply { mkdirs() }
    val task = createTestTask<BuildCodegenCLITask> { it.codegenDir.set(tempFolder.root) }

    assertTrue(task.onlyIf.isSatisfiedBy(task))
  }

  @Test
  fun buildCodegenCli_onlyIf_withExistingDirtyDirectory_isNotSatisfied() {
    File(tempFolder.root, "lib/cli/a-file").apply {
      parentFile.mkdirs()
      writeText("Â¯\\_(ãƒ„)_/Â¯")
    }
    val task = createTestTask<BuildCodegenCLITask> { it.codegenDir.set(tempFolder.root) }

    assertFalse(task.onlyIf.isSatisfiedBy(task))
  }
}


/*
 * 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

import com.facebook.react.tests.OS
import com.facebook.react.tests.OsRule
import com.facebook.react.tests.WithOs
import com.facebook.react.tests.createTestTask
import java.io.File
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class BundleHermesCTaskTest {

  @get:Rule val tempFolder = TemporaryFolder()

  @get:Rule val osRule = OsRule()

  @Test
  fun bundleTask_groupIsSetCorrectly() {
    val task = createTestTask<BundleHermesCTask> {}
    assertEquals("react", task.group)
  }

  @Test
  fun bundleTask_inputFiles_areSetCorrectly() {
    val rootDir =
        tempFolder.newFolder("js").apply {
          File(this, "file.js").createNewFile()
          File(this, "file.jsx").createNewFile()
          File(this, "file.ts").createNewFile()
          File(this, "file.tsx").createNewFile()
        }

    val task = createTestTask<BundleHermesCTask> { it.root.set(rootDir) }

    assertEquals(4, task.sources.files.size)
    assertEquals(
        setOf(
            File(rootDir, "file.js"),
            File(rootDir, "file.jsx"),
            File(rootDir, "file.ts"),
            File(rootDir, "file.tsx")),
        task.sources.files)
  }

  @Test
  fun bundleTask_inputFilesInExcludedPath_areExcluded() {
    fun File.createFileAndPath() {
      parentFile.mkdirs()
      createNewFile()
    }

    val rootDir =
        tempFolder.newFolder("js").apply {
          File(this, "afolder/includedfile.js").createFileAndPath()
          // Those files should be excluded due to their filepath
          File(this, "android/excludedfile.js").createFileAndPath()
          File(this, "ios/excludedfile.js").createFileAndPath()
          File(this, "build/excludedfile.js").createFileAndPath()
          File(this, "node_modules/react-native/excludedfile.js").createFileAndPath()
        }

    val task = createTestTask<BundleHermesCTask> { it.root.set(rootDir) }

    assertEquals(
        setOf(
            "**/android/**/*",
            "**/ios/**/*",
            "**/build/**/*",
            "**/node_modules/**/*",
        ),
        task.sources.excludes)
    assertEquals(1, task.sources.files.size)
    assertEquals(setOf(File(rootDir, "afolder/includedfile.js")), task.sources.files)
  }

  @Test
  fun bundleTask_staticInputs_areSetCorrectly() {
    val task =
        createTestTask<BundleHermesCTask> {
          it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2"))
          it.bundleCommand.set("bundle")
          it.bundleAssetName.set("myassetname")
          it.minifyEnabled.set(true)
          it.hermesEnabled.set(true)
          it.devEnabled.set(true)
          it.extraPackagerArgs.set(listOf("extra", "arg"))
          it.hermesCommand.set("./my-hermesc")
          it.hermesFlags.set(listOf("flag1", "flag2"))
        }

    assertEquals(listOf("node", "arg1", "arg2"), task.nodeExecutableAndArgs.get())
    assertEquals("bundle", task.bundleCommand.get())
    assertEquals("myassetname", task.bundleAssetName.get())
    assertTrue(task.minifyEnabled.get())
    assertTrue(task.hermesEnabled.get())
    assertTrue(task.devEnabled.get())
    assertEquals(listOf("extra", "arg"), task.extraPackagerArgs.get())
    assertEquals("./my-hermesc", task.hermesCommand.get())
    assertEquals(listOf("flag1", "flag2"), task.hermesFlags.get())
  }

  @Test
  fun bundleTask_filesInput_areSetCorrectly() {
    val entryFile = tempFolder.newFile("entry.js")
    val cliFile = tempFolder.newFile("cli.js")
    val jsBundleDir = tempFolder.newFolder("jsbundle")
    val resourcesDir = tempFolder.newFolder("resources")
    val jsIntermediateSourceMapsDir = tempFolder.newFolder("jsIntermediateSourceMaps")
    val jsSourceMapsDir = tempFolder.newFolder("jsSourceMaps")
    val bundleConfig = tempFolder.newFile("bundle.config")
    val reactNativeDir = tempFolder.newFolder("node_modules/react-native")

    val task =
        createTestTask<BundleHermesCTask> {
          it.entryFile.set(entryFile)
          it.cliFile.set(cliFile)
          it.jsBundleDir.set(jsBundleDir)
          it.resourcesDir.set(resourcesDir)
          it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir)
          it.jsSourceMapsDir.set(jsSourceMapsDir)
          it.bundleConfig.set(bundleConfig)
          it.reactNativeDir.set(reactNativeDir)
        }

    assertEquals(entryFile, task.entryFile.get().asFile)
    assertEquals(cliFile, task.cliFile.get().asFile)
    assertEquals(jsBundleDir, task.jsBundleDir.get().asFile)
    assertEquals(resourcesDir, task.resourcesDir.get().asFile)
    assertEquals(jsIntermediateSourceMapsDir, task.jsIntermediateSourceMapsDir.get().asFile)
    assertEquals(jsSourceMapsDir, task.jsSourceMapsDir.get().asFile)
    assertEquals(bundleConfig, task.bundleConfig.get().asFile)
    assertEquals(reactNativeDir, task.reactNativeDir.get().asFile)
  }

  @Test
  fun resolvePackagerSourceMapFile_withHermesEnabled_returnsCorrectFile() {
    val jsIntermediateSourceMapsDir = tempFolder.newFolder("jsIntermediateSourceMaps")
    val bundleAssetName = "myassetname"
    val task =
        createTestTask<BundleHermesCTask> {
          it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir)
          it.hermesEnabled.set(true)
          it.bundleAssetName.set(bundleAssetName)
        }

    assertEquals(
        File(jsIntermediateSourceMapsDir, "myassetname.packager.map"),
        task.resolvePackagerSourceMapFile(bundleAssetName))
  }

  @Test
  fun resolvePackagerSourceMapFile_withHermesDisabled_returnsCorrectFile() {
    val jsSourceMapsDir = tempFolder.newFolder("jsSourceMaps")
    val bundleAssetName = "myassetname"
    val task =
        createTestTask<BundleHermesCTask> {
          it.jsSourceMapsDir.set(jsSourceMapsDir)
          it.hermesEnabled.set(false)
        }

    assertEquals(
        File(jsSourceMapsDir, "myassetname.map"),
        task.resolvePackagerSourceMapFile(bundleAssetName))
  }

  @Test
  fun resolveOutputSourceMap_returnsCorrectFile() {
    val jsSourceMapsDir = tempFolder.newFolder("jsSourceMaps")
    val bundleAssetName = "myassetname"
    val task = createTestTask<BundleHermesCTask> { it.jsSourceMapsDir.set(jsSourceMapsDir) }

    assertEquals(
        File(jsSourceMapsDir, "myassetname.map"), task.resolveOutputSourceMap(bundleAssetName))
  }

  @Test
  fun resolveCompilerSourceMap_returnsCorrectFile() {
    val jsIntermediateSourceMapsDir = tempFolder.newFolder("jsIntermediateSourceMaps")
    val bundleAssetName = "myassetname"
    val task =
        createTestTask<BundleHermesCTask> {
          it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir)
        }

    assertEquals(
        File(jsIntermediateSourceMapsDir, "myassetname.compiler.map"),
        task.resolveCompilerSourceMap(bundleAssetName))
  }

  @Test
  fun getBundleCommand_returnsCorrectCommand() {
    val entryFile = tempFolder.newFile("index.js")
    val cliFile = tempFolder.newFile("cli.js")
    val bundleFile = tempFolder.newFile("bundle.js")
    val sourceMapFile = tempFolder.newFile("bundle.js.map")
    val resourcesDir = tempFolder.newFolder("res")
    val bundleConfig = tempFolder.newFile("bundle.config")
    val task =
        createTestTask<BundleHermesCTask> {
          it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2"))
          it.root.set(tempFolder.root)
          it.cliFile.set(cliFile)
          it.bundleCommand.set("bundle")
          it.devEnabled.set(true)
          it.entryFile.set(entryFile)
          it.resourcesDir.set(resourcesDir)
          it.bundleConfig.set(bundleConfig)
          it.minifyEnabled.set(true)
          it.extraPackagerArgs.set(listOf("--read-global-cache"))
        }

    val bundleCommand = task.getBundleCommand(bundleFile, sourceMapFile)

    assertEquals("node", bundleCommand[0])
    assertEquals("arg1", bundleCommand[1])
    assertEquals("arg2", bundleCommand[2])
    assertEquals(cliFile.absolutePath, bundleCommand[3])
    assertEquals("bundle", bundleCommand[4])
    assertEquals("--platform", bundleCommand[5])
    assertEquals("android", bundleCommand[6])
    assertEquals("--dev", bundleCommand[7])
    assertEquals("true", bundleCommand[8])
    assertEquals("--reset-cache", bundleCommand[9])
    assertEquals("--entry-file", bundleCommand[10])
    assertEquals(entryFile.absolutePath, bundleCommand[11])
    assertEquals("--bundle-output", bundleCommand[12])
    assertEquals(bundleFile.absolutePath, bundleCommand[13])
    assertEquals("--assets-dest", bundleCommand[14])
    assertEquals(resourcesDir.absolutePath, bundleCommand[15])
    assertEquals("--sourcemap-output", bundleCommand[16])
    assertEquals(sourceMapFile.absolutePath, bundleCommand[17])
    assertEquals("--config", bundleCommand[18])
    assertEquals(bundleConfig.absolutePath, bundleCommand[19])
    assertEquals("--minify", bundleCommand[20])
    assertEquals("true", bundleCommand[21])
    assertEquals("--read-global-cache", bundleCommand[22])
    assertEquals("--verbose", bundleCommand[23])
    assertEquals(24, bundleCommand.size)
  }

  @Test
  @WithOs(OS.WIN)
  fun getBundleCommand_onWindows_returnsWinValidCommandsPaths() {
    val entryFile = tempFolder.newFile("index.js")
    val cliFile = tempFolder.newFile("cli.js")
    val bundleFile = tempFolder.newFile("bundle.js")
    val sourceMapFile = tempFolder.newFile("bundle.js.map")
    val resourcesDir = tempFolder.newFolder("res")
    val bundleConfig = tempFolder.newFile("bundle.config")
    val task =
        createTestTask<BundleHermesCTask> {
          it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2"))
          it.root.set(tempFolder.root)
          it.cliFile.set(cliFile)
          it.bundleCommand.set("bundle")
          it.devEnabled.set(true)
          it.entryFile.set(entryFile)
          it.resourcesDir.set(resourcesDir)
          it.bundleConfig.set(bundleConfig)
          it.minifyEnabled.set(true)
          it.extraPackagerArgs.set(listOf("--read-global-cache"))
        }

    val bundleCommand = task.getBundleCommand(bundleFile, sourceMapFile)

    assertEquals("cmd", bundleCommand[0])
    assertEquals("/c", bundleCommand[1])
    assertEquals("node", bundleCommand[2])
    assertEquals("arg1", bundleCommand[3])
    assertEquals("arg2", bundleCommand[4])
    assertEquals(cliFile.relativeTo(tempFolder.root).path, bundleCommand[5])
    assertEquals("bundle", bundleCommand[6])
    assertEquals("--platform", bundleCommand[7])
    assertEquals("android", bundleCommand[8])
    assertEquals("--dev", bundleCommand[9])
    assertEquals("true", bundleCommand[10])
    assertEquals("--reset-cache", bundleCommand[11])
    assertEquals("--entry-file", bundleCommand[12])
    assertEquals(entryFile.relativeTo(tempFolder.root).path, bundleCommand[13])
    assertEquals("--bundle-output", bundleCommand[14])
    assertEquals(bundleFile.relativeTo(tempFolder.root).path, bundleCommand[15])
    assertEquals("--assets-dest", bundleCommand[16])
    assertEquals(resourcesDir.relativeTo(tempFolder.root).path, bundleCommand[17])
    assertEquals("--sourcemap-output", bundleCommand[18])
    assertEquals(sourceMapFile.relativeTo(tempFolder.root).path, bundleCommand[19])
    assertEquals("--config", bundleCommand[20])
    assertEquals(bundleConfig.relativeTo(tempFolder.root).path, bundleCommand[21])
    assertEquals("--minify", bundleCommand[22])
    assertEquals("true", bundleCommand[23])
    assertEquals("--read-global-cache", bundleCommand[24])
    assertEquals("--verbose", bundleCommand[25])
    assertEquals(26, bundleCommand.size)
  }

  @Test
  fun getBundleCommand_withoutConfig_returnsCommandWithoutConfig() {
    val entryFile = tempFolder.newFile("index.js")
    val cliFile = tempFolder.newFile("cli.js")
    val bundleFile = tempFolder.newFile("bundle.js")
    val sourceMapFile = tempFolder.newFile("bundle.js.map")
    val resourcesDir = tempFolder.newFolder("res")
    val task =
        createTestTask<BundleHermesCTask> {
          it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2"))
          it.root.set(tempFolder.root)
          it.cliFile.set(cliFile)
          it.bundleCommand.set("bundle")
          it.devEnabled.set(true)
          it.entryFile.set(entryFile)
          it.resourcesDir.set(resourcesDir)
          it.minifyEnabled.set(true)
          it.extraPackagerArgs.set(listOf("--read-global-cache"))
        }

    val bundleCommand = task.getBundleCommand(bundleFile, sourceMapFile)

    assertTrue("--config" !in bundleCommand)
  }

  @Test
  fun getHermescCommand_returnsCorrectCommand() {
    val customHermesc = "hermesc"
    val bytecodeFile = tempFolder.newFile("bundle.js.hbc")
    val bundleFile = tempFolder.newFile("bundle.js")
    val task =
        createTestTask<BundleHermesCTask> {
          it.root.set(tempFolder.root)
          it.hermesFlags.set(listOf("my-custom-hermes-flag"))
        }

    val hermesCommand = task.getHermescCommand(customHermesc, bytecodeFile, bundleFile)

    assertEquals(customHermesc, hermesCommand[0])
    assertEquals("-emit-binary", hermesCommand[1])
    assertEquals("-max-diagnostic-width=80", hermesCommand[2])
    assertEquals("-out", hermesCommand[3])
    assertEquals(bytecodeFile.absolutePath, hermesCommand[4])
    assertEquals(bundleFile.absolutePath, hermesCommand[5])
    assertEquals("my-custom-hermes-flag", hermesCommand[6])
    assertEquals(7, hermesCommand.size)
  }

  @Test
  @WithOs(OS.WIN)
  fun getHermescCommand_onWindows_returnsRelativePaths() {
    val customHermesc = "hermesc"
    val bytecodeFile = tempFolder.newFile("bundle.js.hbc")
    val bundleFile = tempFolder.newFile("bundle.js")
    val task =
        createTestTask<BundleHermesCTask> {
          it.root.set(tempFolder.root)
          it.hermesFlags.set(listOf("my-custom-hermes-flag"))
        }

    val hermesCommand = task.getHermescCommand(customHermesc, bytecodeFile, bundleFile)

    assertEquals("cmd", hermesCommand[0])
    assertEquals("/c", hermesCommand[1])
    assertEquals(customHermesc, hermesCommand[2])
    assertEquals("-emit-binary", hermesCommand[3])
    assertEquals("-max-diagnostic-width=80", hermesCommand[4])
    assertEquals("-out", hermesCommand[5])
    assertEquals(bytecodeFile.relativeTo(tempFolder.root).path, hermesCommand[6])
    assertEquals(bundleFile.relativeTo(tempFolder.root).path, hermesCommand[7])
    assertEquals("my-custom-hermes-flag", hermesCommand[8])
    assertEquals(9, hermesCommand.size)
  }

  @Test
  fun getComposeSourceMapsCommand_returnsCorrectCommand() {
    val packagerMap = tempFolder.newFile("bundle.js.packager.map")
    val compilerMap = tempFolder.newFile("bundle.js.compiler.map")
    val outputMap = tempFolder.newFile("bundle.js.map")
    val reactNativeDir = tempFolder.newFolder("node_modules/react-native")
    val composeSourceMapsFile = File(reactNativeDir, "scripts/compose-source-maps.js")
    val task =
        createTestTask<BundleHermesCTask> {
          it.root.set(tempFolder.root)
          it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2"))
        }

    val composeSourcemapCommand =
        task.getComposeSourceMapsCommand(composeSourceMapsFile, packagerMap, compilerMap, outputMap)

    assertEquals("node", composeSourcemapCommand[0])
    assertEquals("arg1", composeSourcemapCommand[1])
    assertEquals("arg2", composeSourcemapCommand[2])
    assertEquals(composeSourceMapsFile.absolutePath, composeSourcemapCommand[3])
    assertEquals(packagerMap.absolutePath, composeSourcemapCommand[4])
    assertEquals(compilerMap.absolutePath, composeSourcemapCommand[5])
    assertEquals("-o", composeSourcemapCommand[6])
    assertEquals(outputMap.absolutePath, composeSourcemapCommand[7])
    assertEquals(8, composeSourcemapCommand.size)
  }

  @Test
  @WithOs(OS.WIN)
  fun getComposeSourceMapsCommand_onWindows_returnsRelativePaths() {
    val packagerMap = tempFolder.newFile("bundle.js.packager.map")
    val compilerMap = tempFolder.newFile("bundle.js.compiler.map")
    val outputMap = tempFolder.newFile("bundle.js.map")
    val reactNativeDir = tempFolder.newFolder("node_modules/react-native")
    val composeSourceMapsFile = File(reactNativeDir, "scripts/compose-source-maps.js")
    val task =
        createTestTask<BundleHermesCTask> {
          it.root.set(tempFolder.root)
          it.nodeExecutableAndArgs.set(listOf("node", "arg1", "arg2"))
        }

    val composeSourcemapCommand =
        task.getComposeSourceMapsCommand(composeSourceMapsFile, packagerMap, compilerMap, outputMap)

    assertEquals("cmd", composeSourcemapCommand[0])
    assertEquals("/c", composeSourcemapCommand[1])
    assertEquals("node", composeSourcemapCommand[2])
    assertEquals("arg1", composeSourcemapCommand[3])
    assertEquals("arg2", composeSourcemapCommand[4])
    assertEquals(composeSourceMapsFile.relativeTo(tempFolder.root).path, composeSourcemapCommand[5])
    assertEquals(packagerMap.relativeTo(tempFolder.root).path, composeSourcemapCommand[6])
    assertEquals(compilerMap.relativeTo(tempFolder.root).path, composeSourcemapCommand[7])
    assertEquals("-o", composeSourcemapCommand[8])
    assertEquals(outputMap.relativeTo(tempFolder.root).path, composeSourcemapCommand[9])
    assertEquals(10, composeSourcemapCommand.size)
  }
}


/*
 * 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

import com.facebook.react.tests.*
import com.facebook.react.tests.createProject
import com.facebook.react.tests.createTestTask
import java.io.File
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class GenerateCodegenArtifactsTaskTest {

  @get:Rule val tempFolder = TemporaryFolder()

  @get:Rule val osRule = OsRule()

  @Test
  fun generateCodegenSchema_inputFiles_areSetCorrectly() {
    val outputDir = tempFolder.newFolder("output")

    val task = createTestTask<GenerateCodegenArtifactsTask> { it.generatedSrcDir.set(outputDir) }

    assertEquals(File(outputDir, "schema.json"), task.generatedSchemaFile.get().asFile)
  }

  @Test
  fun generateCodegenSchema_outputFile_isSetCorrectly() {
    val outputDir = tempFolder.newFolder("output")

    val task = createTestTask<GenerateCodegenArtifactsTask> { it.generatedSrcDir.set(outputDir) }

    assertEquals(File(outputDir, "java"), task.generatedJavaFiles.get().asFile)
    assertEquals(File(outputDir, "jni"), task.generatedJniFiles.get().asFile)
  }

  @Test
  fun generateCodegenSchema_simpleProperties_areInsideInput() {
    val packageJsonFile = tempFolder.newFile("package.json")

    val task =
        createTestTask<GenerateCodegenArtifactsTask> {
          it.nodeExecutableAndArgs.set(listOf("npm", "help"))
          it.codegenJavaPackageName.set("com.example.test")
          it.libraryName.set("example-test")
          it.packageJsonFile.set(packageJsonFile)
        }

    assertEquals(listOf("npm", "help"), task.nodeExecutableAndArgs.get())
    assertEquals("com.example.test", task.codegenJavaPackageName.get())
    assertEquals("example-test", task.libraryName.get())
    assertTrue(task.inputs.properties.containsKey("nodeExecutableAndArgs"))
    assertTrue(task.inputs.properties.containsKey("codegenJavaPackageName"))
    assertTrue(task.inputs.properties.containsKey("libraryName"))
  }

  @Test
  @WithOs(OS.LINUX)
  fun setupCommandLine_willSetupCorrectly() {
    val reactNativeDir = tempFolder.newFolder("node_modules/react-native/")
    val outputDir = tempFolder.newFolder("output")

    val task =
        createTestTask<GenerateCodegenArtifactsTask> {
          it.reactNativeDir.set(reactNativeDir)
          it.generatedSrcDir.set(outputDir)
          it.nodeExecutableAndArgs.set(listOf("--verbose"))
        }

    task.setupCommandLine("example-test", "com.example.test")

    assertEquals(
        listOf(
            "--verbose",
            File(reactNativeDir, "scripts/generate-specs-cli.js").toString(),
            "--platform",
            "android",
            "--schemaPath",
            File(outputDir, "schema.json").toString(),
            "--outputDir",
            outputDir.toString(),
            "--libraryName",
            "example-test",
            "--javaPackageName",
            "com.example.test",
        ),
        task.commandLine.toMutableList())
  }

  @Test
  @WithOs(OS.WIN)
  fun setupCommandLine_onWindows_willSetupCorrectly() {
    val reactNativeDir = tempFolder.newFolder("node_modules/react-native/")
    val outputDir = tempFolder.newFolder("output")

    val project = createProject()
    val task =
        createTestTask<GenerateCodegenArtifactsTask>(project) {
          it.reactNativeDir.set(reactNativeDir)
          it.generatedSrcDir.set(outputDir)
          it.nodeExecutableAndArgs.set(listOf("--verbose"))
        }

    task.setupCommandLine("example-test", "com.example.test")

    assertEquals(
        listOf(
            "cmd",
            "/c",
            "--verbose",
            File(reactNativeDir, "scripts/generate-specs-cli.js")
                .relativeTo(project.projectDir)
                .path,
            "--platform",
            "android",
            "--schemaPath",
            File(outputDir, "schema.json").relativeTo(project.projectDir).path,
            "--outputDir",
            outputDir.relativeTo(project.projectDir).path,
            "--libraryName",
            "example-test",
            "--javaPackageName",
            "com.example.test",
        ),
        task.commandLine.toMutableList())
  }

  @Test
  fun resolveTaskParameters_withConfigInPackageJson_usesIt() {
    val packageJsonFile =
        tempFolder.newFile("package.json").apply {
          // language=JSON
          writeText(
              """
        {
            "name": "@a/libray",
            "codegenConfig": {
                "name": "an-awesome-library",
                "android": {
                  "javaPackageName": "com.awesome.package"
                }
            }
        }
        """
                  .trimIndent())
        }

    val task =
        createTestTask<GenerateCodegenArtifactsTask> {
          it.packageJsonFile.set(packageJsonFile)
          it.codegenJavaPackageName.set("com.example.ignored")
          it.libraryName.set("a-library-name-that-is-ignored")
        }

    val (libraryName, javaPackageName) = task.resolveTaskParameters()

    assertEquals("an-awesome-library", libraryName)
    assertEquals("com.awesome.package", javaPackageName)
  }

  @Test
  fun resolveTaskParameters_withConfigMissingInPackageJson_usesGradleOne() {
    val packageJsonFile =
        tempFolder.newFile("package.json").apply {
          // language=JSON
          writeText(
              """
        {
            "name": "@a/libray",
            "codegenConfig": {
            }
        }
        """
                  .trimIndent())
        }

    val task =
        createTestTask<GenerateCodegenArtifactsTask> {
          it.packageJsonFile.set(packageJsonFile)
          it.codegenJavaPackageName.set("com.example.test")
          it.libraryName.set("a-library-name-from-gradle")
        }

    val (libraryName, javaPackageName) = task.resolveTaskParameters()

    assertEquals("a-library-name-from-gradle", libraryName)
    assertEquals("com.example.test", javaPackageName)
  }

  @Test
  fun resolveTaskParameters_withMissingPackageJson_usesGradleOne() {
    val task =
        createTestTask<GenerateCodegenArtifactsTask> {
          it.packageJsonFile.set(File(tempFolder.root, "package.json"))
          it.codegenJavaPackageName.set("com.example.test")
          it.libraryName.set("a-library-name-from-gradle")
        }

    val (libraryName, javaPackageName) = task.resolveTaskParameters()

    assertEquals("a-library-name-from-gradle", libraryName)
    assertEquals("com.example.test", javaPackageName)
  }
}


/*
 * 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

import com.facebook.react.tests.*
import com.facebook.react.tests.createProject
import com.facebook.react.tests.createTestTask
import java.io.File
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class GenerateCodegenSchemaTaskTest {

  @get:Rule val tempFolder = TemporaryFolder()

  @get:Rule val osRule = OsRule()

  @Test
  fun generateCodegenSchema_inputFiles_areSetCorrectly() {
    val jsRootDir =
        tempFolder.newFolder("js").apply {
          File(this, "file.js").createNewFile()
          File(this, "file.ts").createNewFile()
          File(this, "ignore.txt").createNewFile()
        }

    val task = createTestTask<GenerateCodegenSchemaTask> { it.jsRootDir.set(jsRootDir) }

    assertEquals(jsRootDir, task.jsInputFiles.dir)
    assertEquals(setOf("**/*.js", "**/*.ts"), task.jsInputFiles.includes)
    assertEquals(2, task.jsInputFiles.files.size)
    assertEquals(
        setOf(File(jsRootDir, "file.js"), File(jsRootDir, "file.ts")), task.jsInputFiles.files)
  }

  @Test
  fun generateCodegenSchema_inputFilesInExcludedPath_areExcluded() {
    fun File.createFileAndPath() {
      parentFile.mkdirs()
      createNewFile()
    }

    val jsRootDir =
        tempFolder.newFolder("js").apply {
          File(this, "afolder/includedfile.js").createFileAndPath()
          // Those files should be excluded due to their filepath
          File(this, "afolder/build/generated/source/codegen/anotherfolder/excludedfile.js")
              .createFileAndPath()
          File(this, "afolder/build/generated/assets/react/anotherfolder/excludedfile.js")
              .createFileAndPath()
          File(this, "afolder/build/generated/res/react/anotherfolder/excludedfile.js")
              .createFileAndPath()
          File(this, "afolder/build/generated/sourcemaps/react/anotherfolder/excludedfile.js")
              .createFileAndPath()
          File(this, "afolder/build/intermediates/sourcemaps/react/anotherfolder/excludedfile.js")
              .createFileAndPath()
        }

    val task = createTestTask<GenerateCodegenSchemaTask> { it.jsRootDir.set(jsRootDir) }

    assertEquals(jsRootDir, task.jsInputFiles.dir)
    assertEquals(
        setOf(
            "**/build/ASSETS/**/*",
            "**/build/RES/**/*",
            "**/build/generated/**/*",
            "**/build/intermediates/**/*",
        ),
        task.jsInputFiles.excludes)
    assertEquals(1, task.jsInputFiles.files.size)
    assertEquals(setOf(File(jsRootDir, "afolder/includedfile.js")), task.jsInputFiles.files)
  }

  @Test
  fun generateCodegenSchema_outputFile_isSetCorrectly() {
    val outputDir = tempFolder.newFolder("output")

    val task = createTestTask<GenerateCodegenSchemaTask> { it.generatedSrcDir.set(outputDir) }

    assertEquals(File(outputDir, "schema.json"), task.generatedSchemaFile.get().asFile)
  }

  @Test
  fun generateCodegenSchema_nodeExecutablesArgs_areInsideInput() {
    val task =
        createTestTask<GenerateCodegenSchemaTask> {
          it.nodeExecutableAndArgs.set(listOf("npm", "help"))
        }

    assertEquals(listOf("npm", "help"), task.nodeExecutableAndArgs.get())
    assertTrue(task.inputs.properties.containsKey("nodeExecutableAndArgs"))
  }

  @Test
  fun wipeOutputDir_willCreateOutputDir() {
    val task =
        createTestTask<GenerateCodegenSchemaTask> {
          it.generatedSrcDir.set(File(tempFolder.root, "output"))
        }

    task.wipeOutputDir()

    assertTrue(File(tempFolder.root, "output").exists())
    assertEquals(0, File(tempFolder.root, "output").listFiles()?.size)
  }

  @Test
  fun wipeOutputDir_willWipeOutputDir() {
    val outputDir =
        tempFolder.newFolder("output").apply { File(this, "some-generated-file").createNewFile() }

    val task = createTestTask<GenerateCodegenSchemaTask> { it.generatedSrcDir.set(outputDir) }

    task.wipeOutputDir()

    assertTrue(outputDir.exists())
    assertEquals(0, outputDir.listFiles()?.size)
  }

  @Test
  @WithOs(OS.LINUX)
  fun setupCommandLine_willSetupCorrectly() {
    val codegenDir = tempFolder.newFolder("codegen")
    val jsRootDir = tempFolder.newFolder("js")
    val outputDir = tempFolder.newFolder("output")

    val task =
        createTestTask<GenerateCodegenSchemaTask> {
          it.codegenDir.set(codegenDir)
          it.jsRootDir.set(jsRootDir)
          it.generatedSrcDir.set(outputDir)
          it.nodeExecutableAndArgs.set(listOf("node", "--verbose"))
        }

    task.setupCommandLine()

    assertEquals(
        listOf(
            "node",
            "--verbose",
            File(codegenDir, "lib/cli/combine/combine-js-to-schema-cli.js").toString(),
            "--platform",
            "android",
            File(outputDir, "schema.json").toString(),
            jsRootDir.toString(),
        ),
        task.commandLine.toMutableList())
  }

  @Test
  @WithOs(OS.WIN)
  fun setupCommandLine_onWindows_willSetupCorrectly() {
    val codegenDir = tempFolder.newFolder("codegen")
    val jsRootDir = tempFolder.newFolder("js")
    val outputDir = tempFolder.newFolder("output")

    val project = createProject()
    val task =
        createTestTask<GenerateCodegenSchemaTask>(project) {
          it.codegenDir.set(codegenDir)
          it.jsRootDir.set(jsRootDir)
          it.generatedSrcDir.set(outputDir)
          it.nodeExecutableAndArgs.set(listOf("node", "--verbose"))
        }

    task.setupCommandLine()

    assertEquals(
        listOf(
            "cmd",
            "/c",
            "node",
            "--verbose",
            File(codegenDir, "lib/cli/combine/combine-js-to-schema-cli.js")
                .relativeTo(project.projectDir)
                .path,
            "--platform",
            "android",
            File(outputDir, "schema.json").relativeTo(project.projectDir).path,
            jsRootDir.relativeTo(project.projectDir).path,
        ),
        task.commandLine.toMutableList())
  }
}


/*
 * 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.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class PrepareBoostTaskTest {

  @get:Rule val tempFolder = TemporaryFolder()

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

    task.taskAction()
  }

  @Test
  fun prepareBoostTask_copiesCMakefile() {
    val boostpath = tempFolder.newFolder("boostpath")
    val output = tempFolder.newFolder("output")
    val project = createProject()
    val task =
        createTestTask<PrepareBoostTask>(project = project) {
          it.boostPath.setFrom(boostpath)
          it.boostVersion.set("1.0.0")
          it.outputDir.set(output)
        }
    File(project.projectDir, "src/main/jni/third-party/boost/CMakeLists.txt").apply {
      parentFile.mkdirs()
      createNewFile()
    }
    task.taskAction()

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

  @Test
  fun prepareBoostTask_copiesAsmFiles() {
    val boostpath = tempFolder.newFolder("boostpath")
    val output = tempFolder.newFolder("output")
    val task =
        createTestTask<PrepareBoostTask>() {
          it.boostPath.setFrom(boostpath)
          it.boostVersion.set("1.0.0")
          it.outputDir.set(output)
        }
    File(boostpath, "asm/asm.S").apply {
      parentFile.mkdirs()
      createNewFile()
    }
    task.taskAction()

    assertTrue(File(output, "asm/asm.S").exists())
  }

  @Test
  fun prepareBoostTask_copiesBoostSourceFiles() {
    val boostpath = tempFolder.newFolder("boostpath")
    val output = tempFolder.newFolder("output")
    val task =
        createTestTask<PrepareBoostTask> {
          it.boostPath.setFrom(boostpath)
          it.boostVersion.set("1.0.0")
          it.outputDir.set(output)
        }
    File(boostpath, "boost_1.0.0/boost/config.hpp").apply {
      parentFile.mkdirs()
      createNewFile()
    }
    task.taskAction()

    assertTrue(File(output, "boost_1.0.0/boost/config.hpp").exists())
  }

  @Test
  fun prepareBoostTask_copiesVersionlessBoostSourceFiles() {
    val boostpath = tempFolder.newFolder("boostpath")
    val output = tempFolder.newFolder("output")
    val task =
        createTestTask<PrepareBoostTask> {
          it.boostPath.setFrom(boostpath)
          it.boostVersion.set("1.0.0")
          it.outputDir.set(output)
        }
    File(boostpath, "boost/boost/config.hpp").apply {
      parentFile.mkdirs()
      createNewFile()
    }
    task.taskAction()

    assertTrue(File(output, "boost_1.0.0/boost/config.hpp").exists())
  }
}


/*
 * 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.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class PrepareGlogTaskTest {

  @get:Rule val tempFolder = TemporaryFolder()

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

    task.taskAction()
  }

  @Test
  fun prepareGlogTask_copiesCMakefile() {
    val glogpath = tempFolder.newFolder("glogpath")
    val output = tempFolder.newFolder("output")
    val project = createProject()
    val task =
        createTestTask<PrepareGlogTask>(project = project) {
          it.glogPath.setFrom(glogpath)
          it.glogVersion.set("1.0.0")
          it.outputDir.set(output)
        }
    File(project.projectDir, "src/main/jni/third-party/glog/CMakeLists.txt").apply {
      parentFile.mkdirs()
      createNewFile()
    }
    task.taskAction()

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

  @Test
  fun prepareGlogTask_copiesConfigHeaderFile() {
    val glogpath = tempFolder.newFolder("glogpath")
    val output = tempFolder.newFolder("output")
    val project = createProject()
    val task =
        createTestTask<PrepareGlogTask>(project = project) {
          it.glogPath.setFrom(glogpath)
          it.glogVersion.set("1.0.0")
          it.outputDir.set(output)
        }
    File(project.projectDir, "src/main/jni/third-party/glog/config.h").apply {
      parentFile.mkdirs()
      createNewFile()
    }
    task.taskAction()

    assertTrue(output.listFiles()!!.any { it.name == "config.h" })
  }

  @Test
  fun prepareGlogTask_copiesSourceCode() {
    val glogpath = tempFolder.newFolder("glogpath")
    val output = tempFolder.newFolder("output")
    val task =
        createTestTask<PrepareGlogTask> {
          it.glogPath.setFrom(glogpath)
          it.glogVersion.set("1.0.0")
          it.outputDir.set(output)
        }
    File(glogpath, "glog-1.0.0/src/glog.cpp").apply {
      parentFile.mkdirs()
      createNewFile()
    }

    task.taskAction()

    assertTrue(File(output, "glog-1.0.0/src/glog.cpp").exists())
  }

  @Test
  fun prepareGlogTask_replacesTokenCorrectly() {
    val glogpath = tempFolder.newFolder("glogpath")
    val output = tempFolder.newFolder("output")
    val task =
        createTestTask<PrepareGlogTask> {
          it.glogPath.setFrom(glogpath)
          it.glogVersion.set("1.0.0")
          it.outputDir.set(output)
        }
    File(glogpath, "glog-1.0.0/src/glog.h.in").apply {
      parentFile.mkdirs()
      writeText("ac_google_start_namespace")
    }

    task.taskAction()

    val expectedFile = File(output, "glog.h")
    assertTrue(expectedFile.exists())
    assertEquals("ac_google_start_namespace", expectedFile.readText())
  }

  @Test
  fun prepareGlogTask_exportsHeaderCorrectly() {
    val glogpath = tempFolder.newFolder("glogpath")
    val output = tempFolder.newFolder("output")
    val task =
        createTestTask<PrepareGlogTask> {
          it.glogPath.setFrom(glogpath)
          it.glogVersion.set("1.0.0")
          it.outputDir.set(output)
        }
    File(glogpath, "glog-1.0.0/src/logging.h.in").apply {
      parentFile.mkdirs()
      writeText("ac_google_start_namespace")
    }

    task.taskAction()

    assertTrue(File(output, "exported/glog/logging.h").exists())
  }
}


/*
 * 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 com.facebook.react.tests.zipFiles
import java.io.*
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class PrepareJSCTaskTest {

  @get:Rule val tempFolder = TemporaryFolder()

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

    task.taskAction()
  }

  @Test(expected = IllegalStateException::class)
  fun prepareJSCTask_withNullPackage_fails() {
    val task = createTestTask<PrepareJSCTask> { it.jscPackagePath.set(null as String?) }

    task.taskAction()
  }

  @Test(expected = IllegalStateException::class)
  fun prepareJSCTask_withMissingDistFolder_fails() {
    val task =
        createTestTask<PrepareJSCTask> { it.jscPackagePath.set(tempFolder.root.absolutePath) }

    task.taskAction()
  }

  @Test
  fun prepareJSCTask_ignoresEmptyDirs() {
    prepareInputFolder()
    val output = tempFolder.newFolder("output")
    File(tempFolder.root, "dist/just/an/empty/folders/").apply { mkdirs() }

    val task =
        createTestTask<PrepareJSCTask> {
          it.jscPackagePath.set(tempFolder.root.absolutePath)
          it.outputDir.set(output)
        }

    task.taskAction()

    assertFalse(File(output, "just/an/empty/folders/").exists())
  }

  @Test
  fun prepareJSCTask_copiesSoFiles() {
    val soFile = tempFolder.newFile("libsomething.so")
    prepareInputFolder(aarContent = listOf(soFile))
    val output = tempFolder.newFolder("output")

    val task =
        createTestTask<PrepareJSCTask> {
          it.jscPackagePath.set(tempFolder.root.absolutePath)
          it.outputDir.set(output)
        }

    task.taskAction()

    assertEquals("libsomething.so", output.listFiles()?.first()?.name)
  }

  @Test
  fun prepareJSCTask_copiesHeaderFilesToCorrectFolder() {
    prepareInputFolder()
    File(tempFolder.root, "dist/include/justaheader.h").apply {
      parentFile.mkdirs()
      createNewFile()
    }
    val output = tempFolder.newFolder("output")

    val task =
        createTestTask<PrepareJSCTask> {
          it.jscPackagePath.set(tempFolder.root.absolutePath)
          it.outputDir.set(output)
        }

    task.taskAction()

    assertTrue(File(output, "JavaScriptCore/justaheader.h").exists())
  }

  @Test
  fun prepareJSCTask_copiesCMakefile() {
    val project = createProject()
    prepareInputFolder()
    File(project.projectDir, "src/main/jni/third-party/jsc/CMakeLists.txt").apply {
      parentFile.mkdirs()
      createNewFile()
    }
    val output = tempFolder.newFolder("output")

    val task =
        createTestTask<PrepareJSCTask>(project = project) {
          it.jscPackagePath.set(tempFolder.root.absolutePath)
          it.outputDir.set(output)
        }

    task.taskAction()

    assertTrue(File(output, "CMakeLists.txt").exists())
  }

  private fun prepareInputFolder(aarContent: List<File> = listOf(tempFolder.newFile())) {
    val dist = tempFolder.newFolder("dist")
    File(dist, "android-jsc/android-library.aar").apply {
      parentFile.mkdirs()
      createNewFile()
    }
    zipFiles(File(dist, "android-jsc/android-library.aar"), aarContent)
  }
}


/*
 * 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.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class PrepareLibeventTaskTest {

  @get:Rule val tempFolder = TemporaryFolder()

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

    task.taskAction()
  }

  @Test
  fun prepareBoostTask_copiesCMakefile() {
    val libeventPath = tempFolder.newFolder("libeventPath")
    val output = tempFolder.newFolder("output")
    val project = createProject()
    val task =
        createTestTask<PrepareLibeventTask>(project = project) {
          it.libeventPath.setFrom(libeventPath)
          it.libeventVersion.set("1.0.0")
          it.outputDir.set(output)
        }
    File(project.projectDir, "src/main/jni/third-party/libevent/CMakeLists.txt").apply {
      parentFile.mkdirs()
      createNewFile()
    }
    task.taskAction()

    assertTrue(File(output, "CMakeLists.txt").exists())
  }

  @Test
  fun prepareBoostTask_copiesConfigFiles() {
    val libeventPath = tempFolder.newFolder("libeventPath")
    val output = tempFolder.newFolder("output")
    val project = createProject()
    val task =
        createTestTask<PrepareLibeventTask>(project = project) {
          it.libeventPath.setFrom(libeventPath)
          it.libeventVersion.set("1.0.0")
          it.outputDir.set(output)
        }
    File(project.projectDir, "src/main/jni/third-party/libevent/event-config.h").apply {
      parentFile.mkdirs()
      createNewFile()
    }
    File(project.projectDir, "src/main/jni/third-party/libevent/evconfig-private.h").createNewFile()

    task.taskAction()

    assertTrue(File(output, "evconfig-private.h").exists())
    assertTrue(File(output, "include/event2/event-config.h").exists())
  }

  @Test
  fun prepareBoostTask_copiesSourceFiles() {
    val libeventPath = tempFolder.newFolder("libeventPath")
    val output = tempFolder.newFolder("output")
    val task =
        createTestTask<PrepareLibeventTask> {
          it.libeventPath.setFrom(libeventPath)
          it.libeventVersion.set("1.0.0")
          it.outputDir.set(output)
        }
    File(libeventPath, "libevent-1.0.0-stable/sample.c").apply {
      parentFile.mkdirs()
      createNewFile()
    }
    File(libeventPath, "libevent-1.0.0-stable/sample.h").apply {
      parentFile.mkdirs()
      createNewFile()
    }
    File(libeventPath, "libevent-1.0.0-stable/include/sample.h").apply {
      parentFile.mkdirs()
      createNewFile()
    }

    task.taskAction()

    assertTrue(File(output, "sample.c").exists())
    assertTrue(File(output, "sample.h").exists())
    assertTrue(File(output, "include/sample.h").exists())
  }
}


/*
 * 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.tasks.internal.utils.PrefabPreprocessingEntry
import com.facebook.react.tests.createProject
import com.facebook.react.tests.createTestTask
import java.io.*
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder

class PreparePrefabHeadersTaskTest {

  @get:Rule val tempFolder = TemporaryFolder()

  @Test
  fun preparePrefabHeadersTask_withMissingConfiguration_doesNothing() {
    val task = createTestTask<PreparePrefabHeadersTask>()

    task.taskAction()
  }

  @Test
  fun preparePrefabHeadersTask_withSingleEntry_copiesHeaderFile() {
    val outputDir = tempFolder.newFolder("output")
    File(tempFolder.root, "input/hello.h").createNewPathAndFile()

    val project = createProject(projectDir = tempFolder.root)
    val task =
        createTestTask<PreparePrefabHeadersTask>(project = project) {
          it.outputDir.set(outputDir)
          it.input.set(listOf(PrefabPreprocessingEntry("sample_library", "input/" to "")))
        }

    task.taskAction()

    assertTrue(File(outputDir, "sample_library/hello.h").exists())
  }

  @Test
  fun preparePrefabHeadersTask_withSingleEntry_respectsPrefix() {
    val expectedPrefix = "react/render/something/"
    val outputDir = tempFolder.newFolder("output")
    File(tempFolder.root, "input/hello.h").createNewPathAndFile()

    val project = createProject(projectDir = tempFolder.root)
    val task =
        createTestTask<PreparePrefabHeadersTask>(project = project) {
          it.outputDir.set(outputDir)
          it.input.set(
              listOf(PrefabPreprocessingEntry("sample_library", "input/" to expectedPrefix)))
        }

    task.taskAction()

    assertTrue(File(outputDir, "sample_library/${expectedPrefix}hello.h").exists())
  }

  @Test
  fun preparePrefabHeadersTask_ignoresUnnecessaryFiles() {
    val expectedPrefix = "react/render/something/"
    val outputDir = tempFolder.newFolder("output")
    File(tempFolder.root, "input/hello.hpp").createNewPathAndFile()
    File(tempFolder.root, "input/hello.cpp").createNewPathAndFile()
    File(tempFolder.root, "input/CMakeLists.txt").createNewPathAndFile()

    val project = createProject(projectDir = tempFolder.root)
    val task =
        createTestTask<PreparePrefabHeadersTask>(project = project) {
          it.outputDir.set(outputDir)
          it.input.set(
              listOf(PrefabPreprocessingEntry("sample_library", "input/" to expectedPrefix)))
        }

    task.taskAction()

    assertFalse(File(outputDir, "sample_library/hello.hpp").exists())
    assertFalse(File(outputDir, "sample_library/hello.cpp").exists())
    assertFalse(File(outputDir, "sample_library/CMakeLists.txt").exists())
  }

  @Test
  fun preparePrefabHeadersTask_withMultiplePaths_copiesHeaderFiles() {
    val outputDir = tempFolder.newFolder("output")
    File(tempFolder.root, "input/component1/hello1.h").createNewPathAndFile()
    File(tempFolder.root, "input/component2/debug/hello2.h").createNewPathAndFile()

    val project = createProject(projectDir = tempFolder.root)
    val task =
        createTestTask<PreparePrefabHeadersTask>(project = project) {
          it.outputDir.set(outputDir)
          it.input.set(
              listOf(
                  PrefabPreprocessingEntry(
                      "sample_library",
                      listOf("input/component1/" to "", "input/component2/" to "")),
              ))
        }

    task.taskAction()

    assertTrue(File(outputDir, "sample_library/hello1.h").exists())
    assertTrue(File(outputDir, "sample_library/debug/hello2.h").exists())
  }

  @Test
  fun preparePrefabHeadersTask_withMultipleEntries_copiesHeaderFiles() {
    val outputDir = tempFolder.newFolder("output")
    File(tempFolder.root, "input/lib1/hello1.h").createNewPathAndFile()
    File(tempFolder.root, "input/lib2/hello2.h").createNewPathAndFile()

    val project = createProject(projectDir = tempFolder.root)
    val task =
        createTestTask<PreparePrefabHeadersTask>(project = project) {
          it.outputDir.set(outputDir)
          it.input.set(
              listOf(
                  PrefabPreprocessingEntry("libraryone", "input/lib1/" to ""),
                  PrefabPreprocessingEntry("librarytwo", "input/lib2/" to "")))
        }

    task.taskAction()

    assertTrue(File(outputDir, "libraryone/hello1.h").exists())
    assertTrue(File(outputDir, "librarytwo/hello2.h").exists())
  }

  @Test
  fun preparePrefabHeadersTask_withReusedHeaders_copiesHeadersTwice() {
    val outputDir = tempFolder.newFolder("output")
    File(tempFolder.root, "input/lib1/hello1.h").createNewPathAndFile()
    File(tempFolder.root, "input/lib2/hello2.h").createNewPathAndFile()
    File(tempFolder.root, "input/shared/sharedheader.h").createNewPathAndFile()

    val project = createProject(projectDir = tempFolder.root)
    val task =
        createTestTask<PreparePrefabHeadersTask>(project = project) {
          it.outputDir.set(outputDir)
          it.input.set(
              listOf(
                  PrefabPreprocessingEntry(
                      "libraryone", listOf("input/lib1/" to "", "input/shared/" to "shared/")),
                  PrefabPreprocessingEntry(
                      "librarytwo", listOf("input/lib2/" to "", "input/shared/" to "shared/")),
              ))
        }

    task.taskAction()

    assertTrue(File(outputDir, "libraryone/hello1.h").exists())
    assertTrue(File(outputDir, "libraryone/shared/sharedheader.h").exists())
    assertTrue(File(outputDir, "librarytwo/hello2.h").exists())
    assertTrue(File(outputDir, "librarytwo/shared/sharedheader.h").exists())
  }

  @Test
  fun preparePrefabHeadersTask_withBoostHeaders_filtersThemCorrectly() {
    val outputDir = tempFolder.newFolder("output")
    File(tempFolder.root, "boost/boost/config.hpp").createNewPathAndFile()
    File(tempFolder.root, "boost/boost/operators.hpp").createNewPathAndFile()
    File(tempFolder.root, "boost/boost/config/default/default.hpp").createNewPathAndFile()
    File(tempFolder.root, "boost/boost/core/core.hpp").createNewPathAndFile()
    File(tempFolder.root, "boost/boost/detail/workaround.hpp").createNewPathAndFile()
    File(tempFolder.root, "boost/boost/preprocessor/preprocessor.hpp").createNewPathAndFile()
    File(tempFolder.root, "boost/boost/preprocessor/detail/preprocessor_detail.hpp")
        .createNewPathAndFile()
    File(tempFolder.root, "boost/boost/anothermodule/wedontuse.hpp").createNewPathAndFile()

    val project = createProject(projectDir = tempFolder.root)
    val task =
        createTestTask<PreparePrefabHeadersTask>(project = project) {
          it.outputDir.set(outputDir)
          it.input.set(listOf(PrefabPreprocessingEntry("sample_library", "boost/" to "")))
        }

    task.taskAction()

    assertTrue(File(outputDir, "sample_library/boost/config.hpp").exists())
    assertTrue(File(outputDir, "sample_library/boost/operators.hpp").exists())
    assertTrue(File(outputDir, "sample_library/boost/config/default/default.hpp").exists())
    assertTrue(File(outputDir, "sample_library/boost/core/core.hpp").exists())
    assertTrue(File(outputDir, "sample_library/boost/detail/workaround.hpp").exists())
    assertTrue(File(outputDir, "sample_library/boost/preprocessor/preprocessor.hpp").exists())
    assertTrue(
        File(outputDir, "sample_library/boost/preprocessor/detail/preprocessor_detail.hpp")
            .exists())
    assertFalse(File(outputDir, "sample_library/boost/anothermodule/wedontuse.hpp").exists())
  }

  private fun File.createNewPathAndFile() {
    parentFile.mkdirs()
    createNewFile()
  }
}


/*
 * 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.utils

import groovy.test.GroovyTestCase.assertEquals
import org.junit.Test

class PrefabPreprocessingEntryTest {

  @Test
  fun secondaryConstructor_createsAList() {
    val sampleEntry =
        PrefabPreprocessingEntry(
            libraryName = "justALibrary", pathToPrefixCouple = "aPath" to "andAPrefix")

    assertEquals(1, sampleEntry.pathToPrefixCouples.size)
    assertEquals("aPath", sampleEntry.pathToPrefixCouples[0].first)
    assertEquals("andAPrefix", sampleEntry.pathToPrefixCouples[0].second)
  }
}


/*
 * 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.tests

import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement

/**
 * A JUnit [TestRule] to override values of [System.getProperties] with the support of the [WithOs]
 * annotation.
 */
class OsRule : TestRule {

  private var retainOs: String? = null
  private var retainArch: String? = null

  override fun apply(statement: Statement, description: Description): Statement {
    return object : Statement() {
      override fun evaluate() {
        val annotation = description.annotations.filterIsInstance<WithOs>().firstOrNull()

        annotation?.os?.propertyName?.let {
          retainOs = System.getProperty(OS_NAME_KEY)
          System.setProperty(OS_NAME_KEY, it)
        }
        annotation?.arch?.let {
          if (it.isNotBlank()) {
            retainArch = System.getProperty(OS_ARCH_KEY)
            System.setProperty(OS_ARCH_KEY, it)
          }
        }
        try {
          statement.evaluate()
        } finally {
          retainOs?.let { System.setProperty(OS_NAME_KEY, it) }
          retainArch?.let { System.setProperty(OS_ARCH_KEY, it) }
        }
      }
    }
  }

  companion object {
    const val OS_NAME_KEY = "os.name"
    const val OS_ARCH_KEY = "os.arch"
  }
}


/*
 * 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.tests

import java.io.*
import java.net.URI
import java.nio.file.FileSystems
import java.nio.file.Files
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.testfixtures.ProjectBuilder

internal fun createProject(projectDir: File? = null): Project {
  val project =
      ProjectBuilder.builder()
          .apply {
            if (projectDir != null) {
              withProjectDir(projectDir)
            }
          }
          .build()

  project.plugins.apply("com.android.library")
  project.plugins.apply("com.facebook.react")
  return project
}

internal inline fun <reified T : Task> createTestTask(
    project: Project = createProject(),
    taskName: String = T::class.java.simpleName,
    crossinline block: (T) -> Unit = {}
): T = project.tasks.register(taskName, T::class.java) { block(it) }.get()

/** A util function to zip a list of files from [contents] inside the zipfile at [destination]. */
internal fun zipFiles(destination: File, contents: List<File>) {
  ZipOutputStream(BufferedOutputStream(FileOutputStream(destination.absolutePath))).use { out ->
    for (file in contents) {
      FileInputStream(file).use { fi ->
        BufferedInputStream(fi).use { origin ->
          val entry = ZipEntry(file.name)
          out.putNextEntry(entry)
          origin.copyTo(out, 1024)
        }
      }
    }
  }
}

/** A util function to create a zip given a list of dummy files path. */
internal fun createZip(dest: File, paths: List<String>) {
  val env = mapOf("create" to "true")
  val uri = URI.create("jar:file:$dest")

  FileSystems.newFileSystem(uri, env).use { zipfs ->
    paths.forEach {
      val zipEntryPath = zipfs.getPath(it)
      val zipEntryFolder = zipEntryPath.subpath(0, zipEntryPath.nameCount - 1)
      Files.createDirectories(zipEntryFolder)
      Files.createFile(zipEntryPath)
    }
  }
}


/*
 * 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.tests

/** Annotation to specify an Operating System to override the "os.name" System Property. */
@Retention(AnnotationRetention.RUNTIME) annotation class WithOs(val os: OS, val arch: String = "")

enum class OS(val propertyName: String) {
  WIN("Windows"),
  MAC("MacOs"),
  LINUX("Linux")
}