Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small project gen updates #1025

Merged
merged 2 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package foundry.gradle

import foundry.common.FoundryKeys
import foundry.gradle.anvil.AnvilMode
import foundry.gradle.artifacts.SgpArtifact
import foundry.gradle.util.PropertyResolver
Expand Down Expand Up @@ -72,12 +73,16 @@ internal constructor(
public val libraryWithVariants: Boolean
get() = booleanProperty("foundry.android.libraryWithVariants")

/** Default namespace prefix for android proejcts if one isn't specified. */
/** Default namespace prefix for android projects if one isn't specified. */
public val defaultNamespacePrefix: String
get() = stringProperty("foundry.android.defaultNamespacePrefix")
get() = optionalStringProperty("foundry.android.defaultNamespacePrefix") ?: defaultPackagePrefix

/** Default package prefix for JVM projects if one isn't specified. */
public val defaultPackagePrefix: String
get() = stringProperty(FoundryKeys.DEFAULT_PACKAGE_PREFIX)

/**
* Indicates that the gradle versions plugin should allow unstable versions. By default unstable
* Indicates that the gradle versions plugin should allow unstable versions. By default, unstable
* versions are excluded due to the frequent androidx alpha/beta/rc cycle noise. Flag-only, value
* is ignored.
*/
Expand Down
1 change: 1 addition & 0 deletions platforms/intellij/compose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ kotlin {
implementation(libs.jewel.bridge)
implementation(libs.kotlin.poet)
implementation(libs.markdown)
implementation(projects.tools.foundryCommon)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,22 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
import foundry.intellij.compose.projectgen.ProjectGenUi
import foundry.intellij.compose.projectgen.ProjectGenUi.ProjectGenApp
import kotlin.io.path.createTempDirectory
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.intui.standalone.theme.IntUiTheme
import org.jetbrains.jewel.ui.component.DefaultButton
import org.jetbrains.jewel.ui.component.Text

fun main() = singleWindowApplication {
var isDark by remember { mutableStateOf(false) }
val projectDir by remember { mutableStateOf(createTempDirectory("project-gen-playground")) }
IntUiTheme(isDark) {
Column(Modifier.background(JewelTheme.globalColors.panelBackground)) {
DefaultButton(modifier = Modifier.padding(16.dp), onClick = { isDark = !isDark }) {
Text("Toggle dark mode")
}
ProjectGenApp(
rootDir = "rootDir",
rootDir = projectDir,
events =
object : ProjectGenUi.Events {
override fun doOKAction() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import com.slack.circuit.runtime.presenter.Presenter
import java.io.File
import foundry.common.FoundryKeys
import java.nio.file.Path
import java.util.Properties
import kotlin.io.path.absolutePathString
import kotlin.io.path.bufferedReader
import kotlin.io.path.exists

internal class ProjectGenPresenter(
private val rootDir: String,
private val rootDir: Path,
private val onDismissDialog: () -> Unit,
private val onSync: () -> Unit,
) : Presenter<ProjectGenScreen.State> {
Expand All @@ -38,14 +43,24 @@ internal class ProjectGenPresenter(
validationRegex = Regex("[a-zA-Z]([A-Za-z0-9\\-_:.])+"),
)

private val defaultNamespacePrefix by lazy {
rootDir
.resolve("gradle.properties")
.takeIf { it.exists() }
?.let { propertiesPath ->
Properties()
.apply { propertiesPath.bufferedReader().use(::load) }
.getProperty(FoundryKeys.DEFAULT_PACKAGE_PREFIX)
} ?: "foundry"
}

private val packageName =
TextElement(
"",
"Package Name",
// TODO make the prefix configurable
description =
"The project package name (must start with 'slack.') This is used for both source packages and android.namespace.",
prefixTransformation = "slack.",
"The project package name (must start with '$defaultNamespacePrefix.') This is used for both source packages and android.namespace.",
prefixTransformation = "$defaultNamespacePrefix.",
validationRegex = Regex("[A-Za-z0-9.]+"),
)

Expand Down Expand Up @@ -105,7 +120,7 @@ internal class ProjectGenPresenter(
private val uiElements =
mutableStateListOf(
SectionElement("Path Details", "(Required)"),
TextElement(rootDir, "Project root dir", readOnly = true),
TextElement(rootDir.absolutePathString(), "Project root dir", readOnly = true),
path,
packageName,
DividerElement,
Expand Down Expand Up @@ -177,10 +192,9 @@ internal class ProjectGenPresenter(

private fun generate(): Boolean {
return generate(
rootDir = File(rootDir),
rootDir = rootDir,
path = ":${path.value}",
// TODO make this configurable
packageName = "slack.${packageName.value}",
packageName = "$defaultNamespacePrefix.${packageName.value}",
android = android.isChecked,
androidFeatures =
buildSet {
Expand All @@ -206,7 +220,7 @@ internal class ProjectGenPresenter(

@Suppress("LongParameterList")
private fun generate(
rootDir: File,
rootDir: Path,
path: String,
packageName: String,
android: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import androidx.compose.ui.window.Popup
import com.slack.circuit.foundation.Circuit
import com.slack.circuit.foundation.CircuitContent
import com.slack.circuit.runtime.ui.ui
import java.nio.file.Path
import javax.swing.JComponent
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.ui.Orientation
Expand All @@ -67,15 +68,15 @@ object ProjectGenUi {
fun dismissDialogAndSync()
}

fun createPanel(rootDir: String, width: Int, height: Int, events: Events): JComponent {
fun createPanel(rootDir: Path, width: Int, height: Int, events: Events): JComponent {
return ComposePanel().apply {
setBounds(0, 0, width, height)
setContent { FoundryDesktopTheme { ProjectGenApp(rootDir, events) } }
}
}

@Composable
fun ProjectGenApp(rootDir: String, events: Events) {
fun ProjectGenApp(rootDir: Path, events: Events) {
val circuit = remember {
Circuit.Builder()
.addPresenterFactory { _, _, _ ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@
package foundry.intellij.compose.projectgen

import com.squareup.kotlinpoet.FileSpec
import java.io.File
import java.nio.file.Path
import kotlin.io.path.bufferedWriter
import kotlin.io.path.createDirectories
import kotlin.io.path.createFile
import kotlin.io.path.exists
import kotlin.io.path.name
import kotlin.io.path.readLines
import kotlin.io.path.writeText

internal data class Project(
// Gradle path
Expand All @@ -26,13 +33,14 @@ internal data class Project(
val features: List<Feature>,
) {

fun checkValidPath(rootDir: File): Boolean {
fun checkValidPath(rootDir: Path): Boolean {
val projectDir = rootDir.resolve(path.removePrefix(":").replace(":", "/"))
return !projectDir.exists()
}

fun writeTo(rootDir: File) {
val projectDir = rootDir.resolve(path.removePrefix(":").replace(":", "/")).apply { mkdirs() }
fun writeTo(rootDir: Path) {
val projectDir =
rootDir.resolve(path.removePrefix(":").replace(":", "/")).apply { createDirectories() }
buildFile.buildFileSpec(features).writeTo(projectDir)

readMeFile.writeTo(projectDir)
Expand All @@ -41,7 +49,7 @@ internal data class Project(
feature.renderFiles(projectDir)
}

val settingsFile = File(rootDir, "settings-all.gradle.kts")
val settingsFile = rootDir.resolve("settings-all.gradle.kts")
val includedProjects =
settingsFile
.readLines()
Expand Down Expand Up @@ -88,23 +96,23 @@ internal data class BuildFile(val dependencies: List<Dependency>) {
endControlFlow()
}

// slack features
val slackFeatures = features.filterIsInstance<SlackFeatureVisitor>()
val slackAndroidFeatures = features.filterIsInstance<SlackAndroidFeatureVisitor>()
if (slackFeatures.isNotEmpty() || slackAndroidFeatures.isNotEmpty()) {
// foundry features
val foundryFeatures = features.filterIsInstance<FoundryFeatureVisitor>()
val foundryAndroidFeatures = features.filterIsInstance<FoundryAndroidFeatureVisitor>()
if (foundryFeatures.isNotEmpty() || foundryAndroidFeatures.isNotEmpty()) {
addStatement("")
beginControlFlow("foundry")
if (slackFeatures.isNotEmpty()) {
if (foundryFeatures.isNotEmpty()) {
beginControlFlow("features")
for (feature in slackFeatures) {
feature.writeToSlackFeatures(this)
for (feature in foundryFeatures) {
feature.writeToFoundryFeatures(this)
}
endControlFlow()
}
if (slackAndroidFeatures.isNotEmpty()) {
if (foundryAndroidFeatures.isNotEmpty()) {
beginControlFlow("android")
beginControlFlow("features")
slackAndroidFeatures.forEach { it.writeToSlackAndroidFeatures(this) }
foundryAndroidFeatures.forEach { it.writeToFoundryAndroidFeatures(this) }
endControlFlow()
endControlFlow()
}
Expand Down Expand Up @@ -154,22 +162,22 @@ internal data class Dependency(
}

internal interface Feature {
fun renderFiles(projectDir: File) {}
fun renderFiles(projectDir: Path) {}
}

internal interface PluginVisitor {
// Callback within plugins { } block
fun writeToPlugins(builder: FileSpec.Builder)
}

internal interface SlackFeatureVisitor {
// Callback within slack.features { } block
fun writeToSlackFeatures(builder: FileSpec.Builder)
internal interface FoundryFeatureVisitor {
// Callback within foundry.features { } block
fun writeToFoundryFeatures(builder: FileSpec.Builder)
}

internal interface SlackAndroidFeatureVisitor {
// Callback within slack.android.features { } block
fun writeToSlackAndroidFeatures(builder: FileSpec.Builder)
internal interface FoundryAndroidFeatureVisitor {
// Callback within foundry.android.features { } block
fun writeToFoundryAndroidFeatures(builder: FileSpec.Builder)
}

internal interface AndroidBuildFeatureVisitor {
Expand All @@ -190,7 +198,7 @@ internal data class AndroidLibraryFeature(
val viewBindingEnabled: Boolean,
val androidTest: Boolean,
val packageName: String,
) : Feature, PluginVisitor, AndroidBuildFeatureVisitor, SlackAndroidFeatureVisitor {
) : Feature, PluginVisitor, AndroidBuildFeatureVisitor, FoundryAndroidFeatureVisitor {
override fun writeToPlugins(builder: FileSpec.Builder) {
builder.addStatement("alias(libs.plugins.android.library)")
}
Expand All @@ -206,20 +214,21 @@ internal data class AndroidLibraryFeature(
}
}

override fun writeToSlackAndroidFeatures(builder: FileSpec.Builder) {
override fun writeToFoundryAndroidFeatures(builder: FileSpec.Builder) {
if (androidTest) {
builder.addStatement("androidTest()")
}
resourcesPrefix?.let { builder.addStatement("resources(\"$it\")") }
}

override fun renderFiles(projectDir: File) {
override fun renderFiles(projectDir: Path) {
if (androidTest) {
val androidTestDir = File(projectDir, "src/androidTest")
androidTestDir.mkdirs()
val androidTestDir = projectDir.resolve("src/androidTest")
androidTestDir.createDirectories()

// Write the manifest file
File(androidTestDir, "AndroidManifest.xml")
androidTestDir
.resolve("AndroidManifest.xml")
// language=XML
.writeText(
"""
Expand All @@ -245,15 +254,16 @@ internal data class KotlinFeature(val packageName: String, val isAndroid: Boolea
builder.addStatement("alias(libs.plugins.kotlin.$marker)")
}

override fun renderFiles(projectDir: File) {
override fun renderFiles(projectDir: Path) {
writePlaceholderFileTo(projectDir.resolve("src/main"), packageName)
}
}

private fun writePlaceholderFileTo(sourceSetDir: File, packageName: String) {
private fun writePlaceholderFileTo(sourceSetDir: Path, packageName: String) {
val mainSrcDir =
sourceSetDir.resolve("kotlin/${packageName.replace(".", "/")}").apply { mkdirs() }
File(mainSrcDir, "Placeholder.kt")
sourceSetDir.resolve("kotlin/${packageName.replace(".", "/")}").apply { createDirectories() }
mainSrcDir
.resolve("Placeholder.kt")
.writeText(
"""
package $packageName
Expand All @@ -265,8 +275,8 @@ private fun writePlaceholderFileTo(sourceSetDir: File, packageName: String) {
)
}

internal data class DaggerFeature(val runtimeOnly: Boolean) : Feature, SlackFeatureVisitor {
override fun writeToSlackFeatures(builder: FileSpec.Builder) {
internal data class DaggerFeature(val runtimeOnly: Boolean) : Feature, FoundryFeatureVisitor {
override fun writeToFoundryFeatures(builder: FileSpec.Builder) {
// All these args are false by default, so only add arguments for enabled ones!
val args =
mapOf("runtimeOnly" to runtimeOnly)
Expand All @@ -277,29 +287,30 @@ internal data class DaggerFeature(val runtimeOnly: Boolean) : Feature, SlackFeat
}
}

internal object RobolectricFeature : Feature, SlackAndroidFeatureVisitor {
override fun writeToSlackAndroidFeatures(builder: FileSpec.Builder) {
internal object RobolectricFeature : Feature, FoundryAndroidFeatureVisitor {
override fun writeToFoundryAndroidFeatures(builder: FileSpec.Builder) {
builder.addStatement("robolectric()")
}
}

internal object ComposeFeature : Feature, SlackFeatureVisitor {
override fun writeToSlackFeatures(builder: FileSpec.Builder) {
internal object ComposeFeature : Feature, FoundryFeatureVisitor {
override fun writeToFoundryFeatures(builder: FileSpec.Builder) {
builder.addStatement("compose()")
}
}

internal object CircuitFeature : Feature, SlackFeatureVisitor {
override fun writeToSlackFeatures(builder: FileSpec.Builder) {
internal object CircuitFeature : Feature, FoundryFeatureVisitor {
override fun writeToFoundryFeatures(builder: FileSpec.Builder) {
builder.addStatement("circuit()")
}
}

internal class ReadMeFile {
fun writeTo(projectDir: File) {
fun writeTo(projectDir: Path) {
val projectName = projectDir.name
File(projectDir, "README.md")
.apply { createNewFile() }
projectDir
.resolve("README.md")
.apply { createFile() }
.bufferedWriter()
.use { writer ->
val underline = "=".repeat(projectName.length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import java.nio.file.Path
import java.nio.file.Paths
import javax.swing.Action
import javax.swing.JComponent
import kotlin.io.path.absolutePathString

class ProjectGenWindow(currentProject: Project, private val event: AnActionEvent) :
DialogWrapper(currentProject), ProjectGenUi.Events {
Expand All @@ -48,12 +47,7 @@ class ProjectGenWindow(currentProject: Project, private val event: AnActionEvent

override fun createCenterPanel(): JComponent {
setSize(600, 800)
return ProjectGenUi.createPanel(
rootDir = projectPath.absolutePathString(),
width = 600,
height = 800,
events = this,
)
return ProjectGenUi.createPanel(rootDir = projectPath, width = 600, height = 800, events = this)
}

/* Disable default OK and Cancel action button in Dialog window. */
Expand Down
Loading