/****************************************************************************
 * nativeBuildProperties.gradle
 *
 * This script contains global settings used by all projects that have
 * native build requirements.  
 *
 * This is NOT a complete build script for those
 * projects; it is only meant to capture items that are common to all so 
 * we don't need to define them over and over again.
 ****************************************************************************/

/****************************************************************************
 * Always apply the cpp and c plugin, for obvious reasons
 ****************************************************************************/
apply plugin: 'cpp'
apply plugin: 'c'

/****************************************************************************
 * Defines the platforms we have to support in Ghidra.  This model is used 
 * for all native builds and should be extended by each module as-needed.
 ****************************************************************************/
model {

	// define the platforms that we support in ghidra
	platforms {
		win32 {
			architecture 'x86'
			operatingSystem 'windows'
		}
		win64 {
			architecture 'x86_64'
			operatingSystem 'windows'
		}
		linux64 {
			architecture 'x86_64'
			operatingSystem 'linux'
		}
		osx64 {
			architecture 'x86_64'
			operatingSystem 'osx'
		}
		openbsd64 {
			architecture 'x86_64'
			operatingSystem 'openbsd'
		}
	}	

	toolChains {
		// Force use of Clang
		clang(Clang) {
			eachPlatform { tools ->
				tools.cppCompiler.executable = "c++"
				tools.linker.executable = "c++"
				println "C++ compiler is [${tools.cppCompiler.executable}]"
				println "Linker is [${tools.linker.executable}]"
			}
		}
	}
}

/*******************************************************************************************
 * Task: verify presence of correct tool chain version for current platform
 ******************************************************************************************/
task CheckToolChain {
	// Native C/Cpp plugins will trigger failure if no tool chain found
	doFirst {
		if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
			// ensure that required MS Visual Studio is installed where expected
			String msg = "Microsoft Visual Studio install not found: ${VISUAL_STUDIO_BASE_DIR}\n" + 
				"Adjust path in Ghidra/GPL/nativeBuildProperties.gradle if needed."
			if (!file(VISUAL_STUDIO_BASE_DIR).exists() || !file(VISUAL_STUDIO_INSTALL_DIR).exists()) {
				throw new GradleException(msg);
			}	
		}
	}
}

/*******************************************************************************************
 * If task1 contains the given platform name, then it needs to be executed before 
 * task2.  The task1 must be of type LinkExecutable or LinkSharedLibrary so that the
 * targetPlatform may be inspected.
 *
 ******************************************************************************************/
def addTaskDependencyForMyPlatform(task1, task2, platform) {
	if (platform.equals(task1.targetPlatform.get().name)) {
		task2.dependsOn task1.path
		task1.dependsOn CheckToolChain
	}
}

/*******************************************************************************************
 * Returns true if the given task is a custom Make task (starts with the platform name and
 * ends with "Make"). 
 *
 * Note that this is a Ghidra-specific task and is not one created dynamically by Gradle.
 *
 ******************************************************************************************/
def isNativeBinaryMakeTask(Task task, String platform) {
	if (task.name.startsWith(platform) && task.name.endsWith("Make")) {
		return true
	}
	return false
}

/*******************************************************************************************
 * Task Rule : builds all the natives in this module for a given platform.
 *
 * Example : gradle buildNatives_win64 will build all win64 native executables and shared libraries.
 *	
 *		NOTE: you must be on the appropriate platform for this to work.
 *
 ******************************************************************************************/
tasks.addRule("Pattern: buildNatives_<platform name>]: build all natives for given platform") { String taskName ->

	if (taskName.startsWith("buildNatives_")) {
		String platform = taskName - "buildNatives_"
		
		task(taskName)  { myTask ->
		
			project.tasks.withType(LinkExecutable) { t ->
				addTaskDependencyForMyPlatform(t, myTask, platform)
			}
			project.tasks.withType(LinkSharedLibrary) { t ->
				addTaskDependencyForMyPlatform(t, myTask, platform)
			}
			project.tasks.each { t -> 
				if (isNativeBinaryMakeTask(t, platform)) {
					myTask.dependsOn t.path
					t.dependsOn CheckToolChain
				}
			}
			
			// add callbacks to look for native build tasks when new tasks are added later
			project.tasks.withType(LinkExecutable).whenTaskAdded { t ->
				addTaskDependencyForMyPlatform(t, myTask, platform)
			}
			project.tasks.withType(LinkSharedLibrary).whenTaskAdded { t ->
				addTaskDependencyForMyPlatform(t, myTask, platform)
			}
			project.tasks.whenTaskAdded { t -> 
				if (isNativeBinaryMakeTask(t, platform)) {
					myTask.dependsOn t.path
					t.dependsOn CheckToolChain
				}
			}
		}
	}
}

/*******************************************************************************************
 * Task Rule : builds all the natives in this module for a given platform and copies the 
 *             results to the bin repo.
 *
 * Example : gradle prebuildNatives_win64 will build all win64 native executables and shared 
 *           libraries and copy the results to the appropriate project/os directory in the bin
 *           repo.
 *			
 *		NOTE: you must be on the appropriate platform for this to work.
 *
 ******************************************************************************************/
tasks.addRule("Pattern: prebuildNatives_<platform name>]: build all natives for given platform") { String taskName ->
	if (taskName.startsWith("prebuildNatives_")) {
		String platform = taskName - "prebuildNatives_"
		
		task(taskName)  { myTask ->		
			dependsOn "buildNatives_$platform"

			doLast {
				copy  {
					from "${projectDir}/build/os/${platform}"
					into "${getProjectLocationInBinRepo(project)}/os/${platform}"
					exclude "*.exp"
					exclude "*.lib"
				}
			}	
		}
	}
}

/*****************************************************************************************
 * The following block of code changes the output directory for native builds
 * to <projectDir>/build/os/<platform>/<executable>  ?? is there a better way to do this?
 ****************************************************************************************/
gradle.taskGraph.whenReady {
		def p = this.project
		p.tasks.withType(LinkExecutable).each { t ->
			File f = t.linkedFile.getAsFile().get()
			String filename = f.getName()
			NativePlatform platform = t.targetPlatform.get()
			String osName = platform.getName()
			t.linkedFile = p.file("build/os/${osName}/$filename")
		}
		p.tasks.withType(LinkSharedLibrary).each { t ->
			File f = t.linkedFile.getAsFile().get()
			String filename = f.getName()
			NativePlatform platform = t.targetPlatform.get()
			String osName = platform.getName()
			t.linkedFile = p.file("build/os/${osName}/$filename")
		}
}

/*****************************************************************************************
 * The following block of code ensures that the buildNatives_<platform> task is used
 * during assembly to ensure that missing toolchain generates an appropriate error
 ****************************************************************************************/
def currentPlatform = getCurrentPlatformName()
assemble.dependsOn "buildNatives_$currentPlatform" 
