Skip to content

Commit

Permalink
Lazier build. Should fix the resource problem too.
Browse files Browse the repository at this point in the history
  • Loading branch information
lagergren committed Jan 21, 2024
1 parent 4311b63 commit fb81564
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 176 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import XdkProperties.Companion.REDACTED
import XdkPropertiesImpl.Companion.REDACTED
import com.fasterxml.jackson.databind.JsonNode
import io.github.rybalkinsd.kohttp.dsl.context.Method
import io.github.rybalkinsd.kohttp.dsl.context.Method.DELETE
Expand Down
199 changes: 89 additions & 110 deletions build-logic/common-plugins/src/main/kotlin/XdkBuildLogic.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.attributes.Category.CATEGORY_ATTRIBUTE
import org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.ProjectLayout
import org.gradle.api.invocation.Gradle
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.LogLevel.LIFECYCLE
import org.gradle.api.provider.Provider
import org.gradle.kotlin.dsl.named
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.file.Path
Expand All @@ -27,13 +24,47 @@ abstract class XdkProjectBuildLogic(protected val project: Project) {
}
}

class XdkBuildLogic(project: Project) : XdkProjectBuildLogic(project) {
class XdkBuildLogic private constructor(project: Project) : XdkProjectBuildLogic(project) {
private val xdkGitHub: GitHubPackages by lazy {
GitHubPackages(project)
}

private val xdkVersions: XdkVersionHandler by lazy {
XdkVersionHandler(project)
}

private val xdkDistributions: XdkDistribution by lazy {
XdkDistribution(project)
}

private val xdkProperties: XdkProperties by lazy {
logger.info("$prefix Created lazy XDK Properties for project ${project.name}")
XdkPropertiesImpl(project)
}

fun props(): XdkProperties {
return xdkProperties
}

fun versions(): XdkVersionHandler {
return xdkVersions
}

fun distro(): XdkDistribution {
return xdkDistributions
}

fun github(): GitHubPackages {
return xdkGitHub
}

fun resolveLocalXdkInstallation(): File {
return findLocalXdkInstallation() ?: throw project.buildException("Could not find local installation of XVM.")
}

companion object {
const val DEFAULT_JAVA_BYTECODE_VERSION = 20
const val XDK_TASK_GROUP_DEBUG = "debug"

// Artifact names for configuration artifacts. Gradle uses these during the build to identify various
// non-default style artifacts, that we use as part of resolvable and consumable configurations.
const val XDK_ARTIFACT_NAME_DISTRIBUTION_ARCHIVE = "xdk-distribution-archive"
const val XDK_ARTIFACT_NAME_JAVATOOLS_FATJAR = "javatools-fatjar"
const val XDK_ARTIFACT_NAME_MACK_DIR = "mack-dir"
Expand All @@ -42,32 +73,22 @@ class XdkBuildLogic(project: Project) : XdkProjectBuildLogic(project) {
private const val XTC_LAUNCHER = "xec"
private const val DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"

private val cache: MutableMap<Project, XdkBuildLogic> = mutableMapOf()

fun resolve(project: Project): XdkBuildLogic {
// Companion objects are guaranteed to be singletons by Kotlin, so this cache works as build global cache.
return cache[project] ?: XdkBuildLogic(project).also {
cache[project] = it
val logger = project.logger
val size = cache.size
val uniqueSize = cache.values.distinct().size
logger.info("${it.prefix} XDK build logic initialized (instances: ${size})")
assert(size == uniqueSize)
}
}
private val singletonCache: MutableMap<Project, XdkBuildLogic> = mutableMapOf()

fun executeCommand(project: Project, vararg args: String): String = project.run {
val output = ByteArrayOutputStream()
val result = project.exec {
commandLine(*args)
standardOutput = output
isIgnoreExitValue = false
fun instanceFor(project: Project): XdkBuildLogic {
if (singletonCache.contains(project)) {
return singletonCache[project]!!
}
if (result.exitValue != 0) {
logger.error("$prefix ERROR: Command '${args.joinToString(" ")}' failed with exit code ${result.exitValue}")
return ""
}
return output.toString().trim()

val instance = XdkBuildLogic(project)
singletonCache[project] = instance
project.logger.info(
"""
${project.prefix} Creating new XdkBuildLogic for project '${project.name}'
(singletonCache) ${System.identityHashCode(singletonCache)}
(project -> instance) ${System.identityHashCode(project)} -> ${System.identityHashCode(instance)}
""".trimIndent())
return instance
}

fun getDateTimeStampWithTz(ms: Long = System.currentTimeMillis()): String {
Expand Down Expand Up @@ -102,56 +123,6 @@ class XdkBuildLogic(project: Project) : XdkProjectBuildLogic(project) {
}.trim()
}
}

private val xdkProperties = XdkProperties(project)

private val xdkGitHub: GitHubPackages by lazy {
GitHubPackages(project)
}

private val xdkVersions: XdkVersionHandler by lazy {
XdkVersionHandler(project)
}

private val xdkDistributions: XdkDistribution by lazy {
XdkDistribution(project)
}

fun versions(): XdkVersionHandler {
return xdkVersions
}

fun distro(): XdkDistribution {
return xdkDistributions
}

fun github(): GitHubPackages {
return xdkGitHub
}

fun resolveLocalXdkInstallation(): File {
return findLocalXdkInstallation() ?: throw project.buildException("Could not find local installation of XVM.")
}

fun rebuildUnicode(): Boolean {
return xdkPropBool("org.xtclang.unicode.rebuild", false)
}

internal fun xdkPropIsSet(key: String): Boolean {
return xdkProperties.has(key)
}

internal fun xdkPropBool(key: String, defaultValue: Boolean? = null): Boolean {
return xdkProperties.get(key, defaultValue?.toString() ?: "false").toBoolean()
}

internal fun xdkPropInt(key: String, defaultValue: Int? = null): Int {
return xdkProperties.get(key, defaultValue?.toString() ?: "0").toInt()
}

internal fun xdkProp(key: String, defaultValue: String? = null): String {
return xdkProperties.get(key, defaultValue)
}
}

// TODO: Can we move these guys to the versions handler?
Expand All @@ -164,57 +135,65 @@ val Gradle.rootGradle: Gradle
return dir
}

val Gradle.rootLayout: ProjectLayout
get() = rootGradle.rootProject.layout
val Gradle.rootLayout: ProjectLayout get() = rootGradle.rootProject.layout

val Project.compositeRootProjectDirectory
get() = gradle.rootLayout.projectDirectory
val Project.compositeRootProjectDirectory: Directory get() = gradle.rootLayout.projectDirectory

val Project.compositeRootBuildDirectory
get() = gradle.rootLayout.buildDirectory
val Project.compositeRootBuildDirectory: DirectoryProperty get() = gradle.rootLayout.buildDirectory

val Project.userInitScriptDirectory
get() = File(gradle.gradleUserHomeDir, "init.d")
val Project.userInitScriptDirectory: File get() = File(gradle.gradleUserHomeDir, "init.d")

val Project.buildRepoDirectory: Provider<Directory>
get() = compositeRootBuildDirectory.dir("repo")
val Project.buildRepoDirectory get() = compositeRootBuildDirectory.dir("repo")

val Project.xdkBuild: XdkBuildLogic
get() = XdkBuildLogic.resolve(this)
val Project.xdkBuildLogicProvider: Provider<XdkBuildLogic> get() = provider {
XdkBuildLogic.instanceFor(this)
}

val Project.prefix
get() = "[$name]"
val Project.xdkBuildLogic: XdkBuildLogic get() = xdkBuildLogicProvider.get()

val Task.prefix
get() = "[${project.name}:$name]"
val Project.prefix: String get() = "[$name]"

val Task.prefix: String get() = "[${project.name}:$name]"

// TODO: A little bit hacky: use a config, but there is a mutual dependency between the lib_xtc and javatools.
// Better to add the resource directory as a source set?
val Project.xdkIconFile: String
get() = "$compositeRootProjectDirectory/javatools_launcher/src/main/c/x.ico"
val Project.xdkIconFile: String get() = "$compositeRootProjectDirectory/javatools_launcher/src/main/c/x.ico"

// TODO: A little bit hacky, for same reason as above.
// Better to add the resource directory as a source set?
val Project.xdkImplicitsPath: String
get() = "$compositeRootProjectDirectory/lib_ecstasy/src/main/resources/implicit.x"
// TODO: A little bit hacky, for same reason as above; Better to add the resource directory as a source set?
val Project.xdkImplicitsPath: String get() = "$compositeRootProjectDirectory/lib_ecstasy/src/main/resources/implicit.x"

val Project.xdkImplicitsFile: File get() = File(xdkImplicitsPath)

fun Project.executeCommand(vararg args: String): String = project.run {
val output = ByteArrayOutputStream()
val result = project.exec {
commandLine(*args)
standardOutput = output
isIgnoreExitValue = false
}
if (result.exitValue != 0) {
logger.error("$prefix ERROR: Command '${args.joinToString(" ")}' failed with exit code ${result.exitValue}")
return ""
}
return output.toString().trim()
}

val Project.xdkImplicitsFile: File
get() = File(xdkImplicitsPath)
// TODO these should probably be lazy for input purposes

fun Project.isXdkPropertySet(key: String): Boolean {
return xdkBuild.xdkPropIsSet(key)
return xdkBuildLogic.props().has(key)
}

fun Project.getXdkPropertyBoolean(key: String, defaultValue: Boolean? = null): Boolean {
return xdkBuild.xdkPropBool(key, defaultValue)
return xdkBuildLogic.props().get(key, defaultValue)
}

fun Project.getXdkPropertyInt(key: String, defaultValue: Int? = null): Int {
return xdkBuild.xdkPropInt(key, defaultValue)
return xdkBuildLogic.props().get(key, defaultValue)
}

fun Project.getXdkProperty(key: String, defaultValue: String? = null): String {
return xdkBuild.xdkProp(key, defaultValue)
return xdkBuildLogic.props().get(key, defaultValue)
}

fun Task.getXdkPropertyBoolean(key: String, defaultValue: Boolean? = null): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class XdkDistribution(project: Project): XdkProjectBuildLogic(project) {
append(project.version)
if (CI_ENABLED) {
val buildNumber = System.getenv(BUILD_NUMBER) ?: ""
val gitCommitHash = XdkBuildLogic.executeCommand(project, "git", "rev-parse", "HEAD")
val gitCommitHash = project.executeCommand("git", "rev-parse", "HEAD")
if (buildNumber.isNotEmpty() || gitCommitHash.isNotEmpty()) {
logger.warn("This is a CI run, BUILD_NUMBER and git hash must both be available: (BUILD_NUMBER='$buildNumber', commit='$gitCommitHash')")
return@buildString
Expand All @@ -51,10 +51,6 @@ class XdkDistribution(project: Project): XdkProjectBuildLogic(project) {
return project.layout.buildDirectory.dir(path)
}

fun shouldPublishPluginToLocalDist(): Boolean {
return project.getXdkPropertyBoolean("org.xtclang.publish.localDist", false)
}

fun shouldCreateWindowsDistribution(): Boolean {
val runDistExe = project.getXdkPropertyBoolean("org.xtclang.install.distExe", false)
if (runDistExe) {
Expand Down
45 changes: 32 additions & 13 deletions build-logic/common-plugins/src/main/kotlin/XdkProperties.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@ import java.util.Properties
* The property helper tries to ensure that no standard or inherited task that derives from, ore
* uses "./gradlew properties" will inadvertently dump secrets to the console.
*/
class XdkProperties(project: Project): XdkProjectBuildLogic(project) {
interface XdkProperties {
fun get(key: String, defaultValue: String? = null): String
fun get(key: String, defaultValue: Int? = 0): Int
fun get(key: String, defaultValue: Boolean? = false): Boolean
fun has(key: String): Boolean
}

class XdkPropertiesImpl(project: Project): XdkProjectBuildLogic(project), XdkProperties {
companion object {
const val REDACTED = "[REDACTED]"

private const val PROPERTIES_EXT = "properties"

/**
Expand Down Expand Up @@ -63,15 +69,26 @@ class XdkProperties(project: Project): XdkProjectBuildLogic(project) {
* in a build run, that isn't declared in a property file. Examples could be testing out
* a new GitHub token, or adding an environment variable that the plugin needs at start
* time (for example, because the project logger outputs aren't inherited by default in
* a plugin, even if it has access to the actual project.logger instance), for the
* a plugin, even if it has access to the actual project logger instance), for the
* project to which the plugin is applied.
*
* If the method fails to find the value of a property, both in its resolved property
* map, and in the system environment, it will return the supplied defaultValue
* parameter. If no default value was supplied, an exception will be thrown, and the
* build breaks.
*/
fun get(key: String, defaultValue: String? = null): String {
override fun get(key: String, defaultValue: String?): String {
logger.lifecycle("$prefix get($key) invoked (props: ${System.identityHashCode(this)})")
if (!key.startsWith("org.xtclang")) {
// TODO: Remove this artificial limitation.
throw project.buildException("ERROR: XdkProperties are currently expected to start with org.xtclang. Remove this artificial limitation.")
}
if (!has(key)) {
return defaultValue?.also {
logger.info("$prefix XdkProperties; resolved property '$key' to its default value.")
} ?: throw project.buildException("ERROR: XdkProperty '$key' has no value, and no default was given.")
}

// First check a system env override
val envKey = toSystemEnvKey(key)
val envValue = System.getenv(envKey)
Expand All @@ -86,18 +103,20 @@ class XdkProperties(project: Project): XdkProjectBuildLogic(project) {
return sysPropValue.toString()
}

// Finally, look up in table or fallback to default value.
val value = properties[key] ?: System.getenv(toSystemEnvKey(key)) ?: defaultValue
if (value == null && defaultValue == null) {
throw project.buildException("ERROR: XdkProperty '$key' has no value, and no default was given.")
}

logger.info("$prefix XdkProperties; resolved property '$key' from properties table (defaultValue: '$defaultValue')")
return value.toString()
return properties[key]!!.toString()
}

override fun get(key: String, defaultValue: Int?): Int {
return get(key, defaultValue?.toString()).toInt()
}

override fun get(key: String, defaultValue: Boolean?): Boolean {
return get(key, defaultValue.toString()).toBoolean()
}

fun has(key: String): Boolean {
return properties[key] != null
override fun has(key: String): Boolean {
return properties[key] != null || System.getenv(toSystemEnvKey(key)) != null || System.getProperty(key) != null
}

override fun toString(): String {
Expand Down
Loading

0 comments on commit fb81564

Please sign in to comment.