Skip to content

Commit 93a8c08

Browse files
committed
feat: implement plugin core and DSL extension
- Created CustomSdkBuildExtension with awsCustomSdkBuild DSL - Added service configuration collection and validation - Implemented operation selection to shape ID mapping - Created plugin registration and extension setup Completes Prompt 5 of implementation plan
1 parent c6fc6cf commit 93a8c08

File tree

4 files changed

+373
-2
lines changed

4 files changed

+373
-2
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.sdk.kotlin.gradle.customsdk
6+
7+
import org.gradle.api.Project
8+
import org.gradle.api.file.FileCollection
9+
10+
/**
11+
* Main extension for the Custom SDK Build plugin.
12+
* Provides the `awsCustomSdkBuild` DSL for users to configure custom SDK generation.
13+
*/
14+
open class CustomSdkBuildExtension(private val project: Project) {
15+
16+
private val serviceConfigurations = mutableMapOf<String, ServiceConfiguration>()
17+
18+
/**
19+
* Configure Amazon S3 operations.
20+
* Example:
21+
* ```
22+
* s3 {
23+
* operations(S3Operation.GetObject, S3Operation.PutObject)
24+
* }
25+
* ```
26+
*/
27+
fun s3(configure: S3ServiceConfiguration.() -> Unit) {
28+
val config = S3ServiceConfiguration().apply(configure)
29+
serviceConfigurations["s3"] = config
30+
}
31+
32+
/**
33+
* Configure Amazon DynamoDB operations.
34+
* Example:
35+
* ```
36+
* dynamodb {
37+
* operations(DynamodbOperation.GetItem, DynamodbOperation.PutItem)
38+
* }
39+
* ```
40+
*/
41+
fun dynamodb(configure: DynamodbServiceConfiguration.() -> Unit) {
42+
val config = DynamodbServiceConfiguration().apply(configure)
43+
serviceConfigurations["dynamodb"] = config
44+
}
45+
46+
/**
47+
* Configure Amazon Lambda operations.
48+
* Example:
49+
* ```
50+
* lambda {
51+
* operations(LambdaOperation.Invoke, LambdaOperation.CreateFunction)
52+
* }
53+
* ```
54+
*/
55+
fun lambda(configure: LambdaServiceConfiguration.() -> Unit) {
56+
val config = LambdaServiceConfiguration().apply(configure)
57+
serviceConfigurations["lambda"] = config
58+
}
59+
60+
/**
61+
* Get all selected operations mapped by service name to operation shape IDs.
62+
* This is used internally by the plugin to generate the custom SDK.
63+
*/
64+
internal fun getSelectedOperations(): Map<String, List<String>> {
65+
return serviceConfigurations.mapValues { (_, config) ->
66+
config.selectedOperations.map { it.shapeId }
67+
}
68+
}
69+
70+
/**
71+
* Validate the current configuration.
72+
* Throws an exception if the configuration is invalid.
73+
*/
74+
internal fun validate() {
75+
if (serviceConfigurations.isEmpty()) {
76+
throw IllegalStateException("No services configured. Please configure at least one service.")
77+
}
78+
79+
serviceConfigurations.forEach { (serviceName, config) ->
80+
if (config.selectedOperations.isEmpty()) {
81+
throw IllegalStateException("No operations selected for service '$serviceName'. Please select at least one operation.")
82+
}
83+
}
84+
}
85+
86+
/**
87+
* Create a dependency notation for the generated custom SDK.
88+
* This returns a FileCollection that can be used in the dependencies block.
89+
*/
90+
internal fun createDependencyNotation(): FileCollection {
91+
// This will be implemented to return the generated SDK files
92+
return project.files() // Placeholder for now
93+
}
94+
}
95+
96+
/**
97+
* Base class for service configurations.
98+
*/
99+
abstract class ServiceConfiguration {
100+
internal val selectedOperations = mutableListOf<OperationConstant>()
101+
}
102+
103+
/**
104+
* Configuration for Amazon S3 operations.
105+
*/
106+
class S3ServiceConfiguration : ServiceConfiguration() {
107+
fun operations(vararg operations: S3Operation) {
108+
selectedOperations.addAll(operations.map { it.constant })
109+
}
110+
}
111+
112+
/**
113+
* Configuration for Amazon DynamoDB operations.
114+
*/
115+
class DynamodbServiceConfiguration : ServiceConfiguration() {
116+
fun operations(vararg operations: DynamodbOperation) {
117+
selectedOperations.addAll(operations.map { it.constant })
118+
}
119+
}
120+
121+
/**
122+
* Configuration for AWS Lambda operations.
123+
*/
124+
class LambdaServiceConfiguration : ServiceConfiguration() {
125+
fun operations(vararg operations: LambdaOperation) {
126+
selectedOperations.addAll(operations.map { it.constant })
127+
}
128+
}
129+
130+
/**
131+
* Represents an operation constant with its Smithy shape ID.
132+
*/
133+
data class OperationConstant(val shapeId: String) {
134+
override fun toString(): String = shapeId
135+
}
136+
137+
/**
138+
* Sample operation constants for Amazon S3.
139+
*/
140+
enum class S3Operation(val constant: OperationConstant) {
141+
GetObject(OperationConstant("com.amazonaws.s3#GetObject")),
142+
PutObject(OperationConstant("com.amazonaws.s3#PutObject")),
143+
DeleteObject(OperationConstant("com.amazonaws.s3#DeleteObject")),
144+
ListObjects(OperationConstant("com.amazonaws.s3#ListObjects")),
145+
CreateBucket(OperationConstant("com.amazonaws.s3#CreateBucket"))
146+
}
147+
148+
/**
149+
* Sample operation constants for Amazon DynamoDB.
150+
*/
151+
enum class DynamodbOperation(val constant: OperationConstant) {
152+
GetItem(OperationConstant("com.amazonaws.dynamodb#GetItem")),
153+
PutItem(OperationConstant("com.amazonaws.dynamodb#PutItem")),
154+
DeleteItem(OperationConstant("com.amazonaws.dynamodb#DeleteItem")),
155+
Query(OperationConstant("com.amazonaws.dynamodb#Query")),
156+
Scan(OperationConstant("com.amazonaws.dynamodb#Scan"))
157+
}
158+
159+
/**
160+
* Sample operation constants for AWS Lambda.
161+
*/
162+
enum class LambdaOperation(val constant: OperationConstant) {
163+
Invoke(OperationConstant("com.amazonaws.lambda#Invoke")),
164+
CreateFunction(OperationConstant("com.amazonaws.lambda#CreateFunction")),
165+
DeleteFunction(OperationConstant("com.amazonaws.lambda#DeleteFunction")),
166+
ListFunctions(OperationConstant("com.amazonaws.lambda#ListFunctions")),
167+
UpdateFunctionCode(OperationConstant("com.amazonaws.lambda#UpdateFunctionCode"))
168+
}

plugins/custom-sdk-build/src/main/kotlin/aws/sdk/kotlin/gradle/customsdk/CustomSdkBuildPlugin.kt

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,97 @@ package aws.sdk.kotlin.gradle.customsdk
66

77
import org.gradle.api.Plugin
88
import org.gradle.api.Project
9+
import org.gradle.api.file.FileCollection
910

1011
/**
1112
* Gradle plugin for generating custom AWS SDK clients containing only selected operations.
1213
*
1314
* This plugin allows users to specify which AWS services and operations they need,
1415
* then generates a custom SDK client with reduced binary size and improved startup times.
16+
*
17+
* Usage:
18+
* ```kotlin
19+
* plugins {
20+
* id("aws.sdk.kotlin.custom-sdk-build")
21+
* }
22+
*
23+
* val customSdk = awsCustomSdkBuild {
24+
* s3 {
25+
* operations(S3Operation.GetObject, S3Operation.PutObject)
26+
* }
27+
*
28+
* dynamodb {
29+
* operations(DynamodbOperation.GetItem, DynamodbOperation.PutItem)
30+
* }
31+
* }
32+
*
33+
* dependencies {
34+
* implementation(customSdk)
35+
* }
36+
* ```
1537
*/
1638
class CustomSdkBuildPlugin : Plugin<Project> {
39+
1740
override fun apply(project: Project) {
18-
// Plugin implementation will be added in subsequent prompts
19-
project.logger.info("Applied AWS SDK Kotlin Custom SDK Build plugin to project ${project.name}")
41+
project.logger.info("Applying AWS SDK Kotlin Custom SDK Build plugin to project ${project.name}")
42+
43+
// Register the extension
44+
val extension = project.extensions.create(
45+
"awsCustomSdkBuild",
46+
CustomSdkBuildExtension::class.java,
47+
project
48+
)
49+
50+
// Configure the plugin after project evaluation
51+
project.afterEvaluate {
52+
configurePlugin(project, extension)
53+
}
2054
}
55+
56+
/**
57+
* Configure the plugin after the project has been evaluated.
58+
* This is where we set up tasks and dependencies based on the user's configuration.
59+
*/
60+
private fun configurePlugin(project: Project, extension: CustomSdkBuildExtension) {
61+
try {
62+
// Validate the extension configuration
63+
extension.validate()
64+
65+
// Log the selected operations for debugging
66+
val selectedOperations = extension.getSelectedOperations()
67+
project.logger.info("Custom SDK configuration:")
68+
selectedOperations.forEach { (service, operations) ->
69+
project.logger.info(" $service: ${operations.size} operations")
70+
operations.forEach { operation ->
71+
project.logger.debug(" - $operation")
72+
}
73+
}
74+
75+
// TODO: In subsequent prompts, we'll add:
76+
// - Task registration for SDK generation
77+
// - Source set configuration
78+
// - Dependency management
79+
80+
} catch (e: Exception) {
81+
project.logger.error("Failed to configure Custom SDK Build plugin: ${e.message}")
82+
throw e
83+
}
84+
}
85+
}
86+
87+
/**
88+
* Extension function to create the awsCustomSdkBuild DSL and return a dependency notation.
89+
* This allows users to write:
90+
*
91+
* ```kotlin
92+
* val customSdk = awsCustomSdkBuild { ... }
93+
* dependencies { implementation(customSdk) }
94+
* ```
95+
*/
96+
fun Project.awsCustomSdkBuild(configure: CustomSdkBuildExtension.() -> Unit): FileCollection {
97+
val extension = extensions.findByType(CustomSdkBuildExtension::class.java)
98+
?: throw IllegalStateException("CustomSdkBuildExtension not found. Make sure the plugin is applied.")
99+
100+
extension.configure()
101+
return extension.createDependencyNotation()
21102
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.sdk.kotlin.gradle.customsdk
6+
7+
import org.gradle.testfixtures.ProjectBuilder
8+
import kotlin.test.Test
9+
import kotlin.test.assertEquals
10+
import kotlin.test.assertFailsWith
11+
import kotlin.test.assertNotNull
12+
import kotlin.test.assertTrue
13+
14+
class CustomSdkBuildExtensionTest {
15+
16+
@Test
17+
fun `extension can be created and configured`() {
18+
val project = ProjectBuilder.builder().build()
19+
val extension = CustomSdkBuildExtension(project)
20+
21+
extension.s3 {
22+
operations(S3Operation.GetObject, S3Operation.PutObject)
23+
}
24+
25+
val selectedOperations = extension.getSelectedOperations()
26+
assertEquals(1, selectedOperations.size)
27+
assertTrue(selectedOperations.containsKey("s3"))
28+
assertEquals(2, selectedOperations["s3"]?.size)
29+
}
30+
31+
@Test
32+
fun `extension supports multiple services`() {
33+
val project = ProjectBuilder.builder().build()
34+
val extension = CustomSdkBuildExtension(project)
35+
36+
extension.s3 {
37+
operations(S3Operation.GetObject)
38+
}
39+
40+
extension.dynamodb {
41+
operations(DynamodbOperation.GetItem, DynamodbOperation.PutItem)
42+
}
43+
44+
val selectedOperations = extension.getSelectedOperations()
45+
assertEquals(2, selectedOperations.size)
46+
assertEquals(1, selectedOperations["s3"]?.size)
47+
assertEquals(2, selectedOperations["dynamodb"]?.size)
48+
}
49+
50+
@Test
51+
fun `validation fails when no services configured`() {
52+
val project = ProjectBuilder.builder().build()
53+
val extension = CustomSdkBuildExtension(project)
54+
55+
assertFailsWith<IllegalStateException> {
56+
extension.validate()
57+
}
58+
}
59+
60+
@Test
61+
fun `validation fails when service has no operations`() {
62+
val project = ProjectBuilder.builder().build()
63+
val extension = CustomSdkBuildExtension(project)
64+
65+
extension.s3 {
66+
// No operations configured
67+
}
68+
69+
assertFailsWith<IllegalStateException> {
70+
extension.validate()
71+
}
72+
}
73+
74+
@Test
75+
fun `validation passes with valid configuration`() {
76+
val project = ProjectBuilder.builder().build()
77+
val extension = CustomSdkBuildExtension(project)
78+
79+
extension.s3 {
80+
operations(S3Operation.GetObject)
81+
}
82+
83+
// Should not throw
84+
extension.validate()
85+
}
86+
87+
@Test
88+
fun `dependency notation can be created`() {
89+
val project = ProjectBuilder.builder().build()
90+
val extension = CustomSdkBuildExtension(project)
91+
92+
val dependencyNotation = extension.createDependencyNotation()
93+
assertNotNull(dependencyNotation)
94+
}
95+
}

plugins/custom-sdk-build/src/test/kotlin/aws/sdk/kotlin/gradle/customsdk/CustomSdkBuildPluginTest.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,31 @@ class CustomSdkBuildPluginTest {
1818
// Verify plugin was applied successfully
1919
assertNotNull(project.plugins.findPlugin(CustomSdkBuildPlugin::class.java))
2020
}
21+
22+
@Test
23+
fun `plugin registers extension`() {
24+
val project = ProjectBuilder.builder().build()
25+
project.plugins.apply("aws.sdk.kotlin.custom-sdk-build")
26+
27+
// Verify extension was registered
28+
val extension = project.extensions.findByType(CustomSdkBuildExtension::class.java)
29+
assertNotNull(extension)
30+
}
31+
32+
@Test
33+
fun `extension can be configured`() {
34+
val project = ProjectBuilder.builder().build()
35+
project.plugins.apply("aws.sdk.kotlin.custom-sdk-build")
36+
37+
val extension = project.extensions.getByType(CustomSdkBuildExtension::class.java)
38+
39+
// Configure the extension
40+
extension.s3 {
41+
operations(S3Operation.GetObject, S3Operation.PutObject)
42+
}
43+
44+
// Verify configuration
45+
val selectedOperations = extension.getSelectedOperations()
46+
assertNotNull(selectedOperations["s3"])
47+
}
2148
}

0 commit comments

Comments
 (0)