
buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'
    }
}

apply plugin: 'com.android.library'

def _nodeTargetSdkVersion = ((rootProject?.ext?.properties?.targetSdkVersion) ?: 28)

android {
    compileSdkVersion ((rootProject?.ext?.properties?.compileSdkVersion) ?: 28)
    buildToolsVersion ((rootProject?.ext?.properties?.buildToolsVersion) ?: "28.0.3")

    defaultConfig {
        minSdkVersion ((rootProject?.ext?.properties?.minSdkVersion) ?: 16)
        targetSdkVersion _nodeTargetSdkVersion
        versionCode 1
        versionName "1.0"
        externalNativeBuild {
            cmake {
                cppFlags ""
                arguments "-DANDROID_STL=c++_shared"
            }
        }
        ndk {
            abiFilters = project(":app").android.defaultConfig.ndk.abiFilters
        }
    }
    
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    
    sourceSets {
        main {
            jniLibs.srcDirs 'libnode/bin/'
        }
        main.assets.srcDirs += '../install/resources/nodejs-modules'
    }
    
    lintOptions {
        abortOnError false
    }
}

repositories {
    mavenCentral()
    google()
    jcenter()
}

dependencies {
    implementation 'com.facebook.react:react-native:+'
}

task CopyNodeProjectAssetsFolder (type:Sync) {
    description "Copies the Node Project to a build folder for manipulation."
    from "${rootProject.projectDir}/../nodejs-assets/nodejs-project"
    into "${rootProject.buildDir}/nodejs-assets/nodejs-project/"
    exclude '**/*~' // temporary files
    exclude '**/.*' // files and dirs starting with .
    exclude '**/*.gz' // gzip files will cause errors on aapt when merging assets.
}

task GenerateNodeProjectAssetsLists {
    dependsOn "CopyNodeProjectAssetsFolder"
    description "Generates a list for runtime copying"
    inputs.file "${rootProject.buildDir}/nodejs-assets/"
    outputs.file "${rootProject.buildDir}/nodejs-assets/file.list"
    outputs.file "${rootProject.buildDir}/nodejs-assets/dir.list"
    doLast{
        delete "${rootProject.buildDir}/nodejs-assets/file.list"
        delete "${rootProject.buildDir}/nodejs-assets/dir.list"

        String file_list = "";
        String dir_list = "";

        def assets_tree = fileTree(dir: "${rootProject.buildDir}/nodejs-assets/")
        assets_tree.include('nodejs-project/**') // Include the node project.
        assets_tree.exclude('**/.*') // Exclude files and dirs starting with .
        assets_tree.exclude('**/*~') // Exclude temporary files.
        assets_tree.visit { assetFile ->
            if (assetFile.isDirectory()) {
                dir_list += "${assetFile.relativePath}\n"
            } else {
                file_list += "${assetFile.relativePath}\n"
            }
        }
        def file_list_path = new File( "${rootProject.buildDir}/nodejs-assets/file.list")
        file_list_path.write file_list
        def dir_list_path = new File( "${rootProject.buildDir}/nodejs-assets/dir.list")
        dir_list_path.write dir_list
    }
}

project.android.sourceSets.main.assets.srcDirs+="${rootProject.buildDir}/nodejs-assets/"


tasks.getByPath(":${project.name}:preBuild").dependsOn GenerateNodeProjectAssetsLists

import org.gradle.internal.os.OperatingSystem;

String shouldRebuildNativeModules = System.getenv('NODEJS_MOBILE_BUILD_NATIVE_MODULES');

if (shouldRebuildNativeModules==null) {
// If the environment variable is not set right now, check if it has been saved to a file.
    def nativeModulesPreferenceFile = file("${rootProject.projectDir}/../nodejs-assets/BUILD_NATIVE_MODULES.txt");
    if (nativeModulesPreferenceFile.exists()) {
        shouldRebuildNativeModules=nativeModulesPreferenceFile.text.trim();
    }
}

if (shouldRebuildNativeModules==null) {
// If build native modules preference is not set, try to find .gyp files to turn it on.
    shouldRebuildNativeModules="0";
    def gyp_files_tree = fileTree(
        dir: "${rootProject.projectDir}/../nodejs-assets/nodejs-project",
        include: "**/*.gyp"
    );
    gyp_files_tree.visit { gypFile ->
        if (!gypFile.isDirectory()) {
            // It's a .gyp file.
            shouldRebuildNativeModules="1";
            gypFile.stopVisiting();
        }
    }
}

if ("1".equals(shouldRebuildNativeModules)) {

    String npmCommandName = 'npm';
    String nodeCommandName = 'node';
    if (OperatingSystem.current().isMacOsX()) {
        // On macOS, npm's and node's locations may not be in the PATH environment variable if gradle is being run
        // by Android Studio. We need npm to build native modules and node to run node-pre-gyp patches, so we use
        // helper scripts that are created when the plugin is installed to run npm and node with the PATH members that
        // were available during the plugin's install.
        try {
            def commandResult = exec {
                commandLine 'command', '-v', 'npm'
                ignoreExitValue = true
            }
            if ( commandResult.getExitValue() != 0 ) {
                // If npm is not found by command, use the helper script.
                logger.warn("Couldn't find npm in the PATH for building native modules. Will try to use a helper script.");
                npmCommandName = '../build-native-modules-MacOS-helper-script-npm.sh';
            }
            commandResult = exec {
                commandLine 'command', '-v', 'node'
                ignoreExitValue = true
            }
            if ( commandResult.getExitValue() != 0 ) {
                // If node is not found by command, use the helper script.
                logger.warn("Couldn't find node in the PATH for building native modules. Will try to use a helper script.");
                nodeCommandName = '../build-native-modules-MacOS-helper-script-node.sh';
            }
        } catch ( Exception e ) {
            throw new GradleException('Something went wrong looking for npm and node by running "command".', e)
        }
    }

    task ApplyPatchScriptToModules (type:Exec) {
        dependsOn "CopyNodeProjectAssetsFolder"
        description "Apply patches to modules to improve compatibility."
        doFirst {
            if (OperatingSystem.current().isMacOsX()) {
                // Copy the helper script for calling node when building in Android Studio on macOS.
                copy {
                    from "${rootProject.projectDir}/../nodejs-assets/build-native-modules-MacOS-helper-script-node.sh"
                    into "${rootProject.buildDir}/nodejs-assets/"
                }
            }
        }
        workingDir "${rootProject.buildDir}/nodejs-assets/nodejs-project/"
        commandLine nodeCommandName, "${project.projectDir}/../scripts/patch-package.js", "${rootProject.buildDir}/nodejs-assets/nodejs-project/node_modules/"
        doLast {
            if (OperatingSystem.current().isMacOsX()) {
                // Deletes the helper script so it doesn't get included in the APK.
                delete "${rootProject.buildDir}/nodejs-assets/build-native-modules-MacOS-helper-script-node.sh"
            }
        }
    }
    GenerateNodeProjectAssetsLists.dependsOn "ApplyPatchScriptToModules"

    def nativeModulesABIs = android.defaultConfig.ndk.abiFilters;
    if (nativeModulesABIs == null) {
        // No abiFilter is defined for the build. Build native modules for eevery architecture.
        nativeModulesABIs = ["armeabi-v7a", "x86", "arm64-v8a", "x86_64"] as Set<String>;
    }

    nativeModulesABIs.each { abi_name ->
        String temp_arch = {
            switch (abi_name) {
                case 'armeabi-v7a':
                    'arm'
                    break
                case 'arm64-v8a':
                    'arm64'
                    break
                default:
                    abi_name
                    break
            }
        }()
        String temp_cc_ver = '4.9';
        String temp_dest_cpu;
        String temp_v8_arch;
        String temp_suffix;
        String temp_toolchain_name;
        switch ( temp_arch )
        {
            case 'arm':
                temp_dest_cpu = "${temp_arch}"
                temp_v8_arch = "${temp_arch}"
                temp_suffix = "${temp_arch}-linux-androideabi"
                temp_toolchain_name = "${temp_suffix}"
                break
            case 'x86':
                temp_dest_cpu = 'ia32'
                temp_v8_arch = 'ia32'
                temp_suffix = 'i686-linux-android'
                temp_toolchain_name = "${temp_arch}"
                break
            case 'x86_64':
                temp_dest_cpu = 'x64'
                temp_v8_arch = 'x64'
                temp_suffix = "${temp_arch}-linux-android"
                temp_toolchain_name = "${temp_arch}"
                break
            case 'arm64':
                temp_dest_cpu = "${temp_arch}"
                temp_v8_arch = "${temp_arch}"
                temp_suffix = 'aarch64-linux-android'
                temp_toolchain_name = 'aarch64'
                break
            default:
                throw new GradleException("Unsupported architecture for nodejs-mobile native modules: ${temp_arch}")
                break
        }

        String ndk_bundle_path = android.ndkDirectory
        String standalone_toolchain = "${rootProject.buildDir}/standalone-toolchains/${temp_toolchain_name}"
        String npm_toolchain_add_to_path = "${rootProject.buildDir}/bin"
        String npm_toolchain_ar = "${standalone_toolchain}/bin/${temp_suffix}-ar"
        String npm_toolchain_cc = "${standalone_toolchain}/bin/${temp_suffix}-clang"
        String npm_toolchain_cxx = "${standalone_toolchain}/bin/${temp_suffix}-clang++"
        String npm_toolchain_link = "${standalone_toolchain}/bin/${temp_suffix}-clang++"

        String npm_gyp_defines = "target_arch=${temp_arch}"
        npm_gyp_defines += " v8_target_arch=${temp_v8_arch}"
        npm_gyp_defines += " android_target_arch=${temp_arch}"
        if (OperatingSystem.current().isMacOsX()) {
            npm_gyp_defines += " host_os=mac OS=android"
        } else if (OperatingSystem.current().isLinux()) {
            npm_gyp_defines += " host_os=linux OS=android"
        } else {
            throw new GradleException("Unsupported opperating system for nodejs-mobile native builds: ${OperatingSystem.current().getName()}")
        }

        task "CopyNodeProjectAssets${abi_name}" {
            description = "Copying node assets and apply patches to build native modules for ${abi_name}."
            inputs.files fileTree (
                dir: "${rootProject.projectDir}/../nodejs-assets/nodejs-project/"
            ).exclude({
                details -> // We shouldn't need to rebuild native code if there are only changes in the Node.js project javascript files.
                    !details.isDirectory() &&
                    details.getPath().endsWith('.js') &&
                    !details.getPath().startsWith('node_modules/')
            })
            outputs.file "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/copy.timestamp"
            doLast {
                delete "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/"
                copy {
                    from "${rootProject.projectDir}/../nodejs-assets/nodejs-project/"
                    into "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/nodejs-project/"
                    // Symlinks to binaries will be resolved by Gradle during the copy, causing build time errors.
                    // The original project's .bin folder will be added to the path while building in the BuildNpmModules tasks.
                    exclude "**/.bin"
                }
                if (OperatingSystem.current().isMacOsX()) {
                    // Copy the helper scripts for calling npm and node when building in Android Studio on macOS.
                    copy {
                        from "${rootProject.projectDir}/../nodejs-assets/build-native-modules-MacOS-helper-script-node.sh"
                        into "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/"
                    }
                    copy {
                        from "${rootProject.projectDir}/../nodejs-assets/build-native-modules-MacOS-helper-script-npm.sh"
                        into "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/"
                    }
                }
                exec {
                    workingDir "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/nodejs-project/"
                    commandLine nodeCommandName, "${project.projectDir}/../scripts/patch-package.js", "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/nodejs-project/node_modules/"
                }
                new File("${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/copy.timestamp").text = "${new Date().format('yyyy-MM-dd HH:mm:ss')}"
            }
        }

        task "MakeToolchain${abi_name}" (type:Exec) {
            description = "Building a native toolchain to compile nodejs-mobile native modules for ${abi_name}."
            executable = "${ndk_bundle_path}/build/tools/make-standalone-toolchain.sh"
            args "--toolchain=${temp_toolchain_name}-${temp_cc_ver}", "--arch=${temp_arch}", "--install-dir=${standalone_toolchain}", "--stl=libc++", "--force", "--platform=android-${_nodeTargetSdkVersion}"
            outputs.file "${standalone_toolchain}"
        }

        task "BuildNpmModules${abi_name}" (type:Exec) {
            dependsOn "CopyNodeProjectAssets${abi_name}"
            dependsOn "MakeToolchain${abi_name}"
            description = "Building native modules for ${abi_name}."
            inputs.file "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/copy.timestamp"
            outputs.file "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/nodejs-project/"
            workingDir "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/nodejs-project/"
            commandLine npmCommandName, '--verbose', 'rebuild', '--build-from-source'
            environment ('npm_config_node_engine', 'v8' )
            environment ('npm_config_nodedir', "${project.projectDir}/libnode/" )
            String npm_gyp_path_to_use; // Check common paths for nodejs-mobile-gyp
            if ( file("${project.projectDir}/../../nodejs-mobile-gyp/bin/node-gyp.js").exists() ) {
                npm_gyp_path_to_use = "${project.projectDir}/../../nodejs-mobile-gyp/bin/node-gyp.js";
            } else {
                npm_gyp_path_to_use = "${project.projectDir}/../node_modules/nodejs-mobile-gyp/bin/node-gyp.js";
            }
            environment ('npm_config_node_gyp', npm_gyp_path_to_use )
            environment ('npm_config_arch', temp_arch)
            environment ('npm_config_platform', 'android')
            environment ('npm_config_format', 'make-android')

            // Adds the original project .bin to the path. It's a workaround
            // to correctly build some modules that depend on symlinked modules,
            // like node-pre-gyp.
            String original_project_bin = "${rootProject.projectDir}/../nodejs-assets/nodejs-project/node_modules/.bin";
            if(file(original_project_bin).exists()) {
                environment ('PATH', "${original_project_bin}" + System.getProperty("path.separator") + "${System.env.PATH}")
            }

            environment ('TOOLCHAIN',"${standalone_toolchain}")
            environment ('AR',"${npm_toolchain_ar}")
            environment ('CC',"${npm_toolchain_cc}")
            environment ('CXX',"${npm_toolchain_cxx}")
            environment ('LINK',"${npm_toolchain_link}")
            environment ('GYP_DEFINES',"${npm_gyp_defines}")
        }
        task "CopyBuiltNpmAssets${abi_name}" (type:Sync) {
            dependsOn "BuildNpmModules${abi_name}"
            description = "Copying node assets with build native modules for ${abi_name}."
            from "${rootProject.buildDir}/nodejs-native-assets-temp-build/nodejs-native-assets-${abi_name}/nodejs-project/"
            into "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/"
            includeEmptyDirs = false
            include '**/*.node'
        }

        task "GenerateNodeNativeAssetsLists${abi_name}" {
            dependsOn "CopyBuiltNpmAssets${abi_name}"
            description "Generates a list for runtime copying"
            inputs.file "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/"
            outputs.file "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/file.list"
            outputs.file "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/dir.list"
            doLast{
                if(!(new File("${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/")).exists()) {
                    // If the native assets folder doesn't exist from the copy task, skip the creation of the file.list
                    return;
                }
                delete "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/file.list"
                delete "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/dir.list"
                String file_list = "";
                String dir_list = "";

                def assets_tree = fileTree(dir: "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/" )
                assets_tree.visit { assetFile ->
                    if (assetFile.isDirectory()) {
                    dir_list+="${assetFile.relativePath}\n"
                    } else {
                    file_list+="${assetFile.relativePath}\n"
                    }
                }
                def file_list_path = new File( "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/file.list")
                file_list_path.write file_list
                def dir_list_path = new File( "${rootProject.buildDir}/nodejs-native-assets/nodejs-native-assets-${abi_name}/dir.list")
                dir_list_path.write dir_list
            }
        }
        tasks.getByPath(":${project.name}:preBuild").dependsOn "GenerateNodeNativeAssetsLists${abi_name}"
    }
    project.android.sourceSets.main.assets.srcDirs+="${rootProject.buildDir}/nodejs-native-assets/"
}