Skip to content

Commit fcf30d2

Browse files
authored
feat(build): service protocol build control (#121)
1 parent 135a912 commit fcf30d2

File tree

2 files changed

+99
-13
lines changed

2 files changed

+99
-13
lines changed

README.md

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ To see list of all projects run `./gradlew projects`
4141
See the local.properties definition above to specify this in a config file.
4242

4343
```sh
44-
./gradlew -Paws.services=lambda :codegen:sdk:bootstrap
44+
./gradlew -Paws.services=+lambda :codegen:sdk:bootstrap
4545
```
4646

4747
##### Testing Locally
@@ -65,20 +65,51 @@ This will output HTML formatted documentation to `build/dokka/htmlMultiModule`
6565

6666
NOTE: You currently need an HTTP server to view the documentation in browser locally. You can either use the builtin server in Intellij or use your favorite local server (e.g. `python3 -m http.server`). See [Kotlin/dokka#1795](https://github.com/Kotlin/dokka/issues/1795)
6767

68-
### Build properties
68+
### Build Properties
6969

7070
You can define a `local.properties` config file at the root of the project to modify build behavior.
7171

72-
An example config with the various properties is below:
72+
|Property|Description|
73+
|---|---|
74+
|`compositeProjects`|Specify paths to repos the SDK depends upon such as `smithy-kotlin`|
75+
|`aws.services`|Specify inclusions (+ prefix) and exclusions (- prefix) of service names to generate|
76+
|`aws.protocols`|Specify inclusions (+ prefix) and exclusions (- prefix) of AWS protocols to generate|
7377

74-
```
78+
#### Composite Projects
79+
80+
Dependencies of the SDK can be added as composite build such that multiple repos may appear as one
81+
holistic source project in the IDE.
82+
83+
```ini
7584
# comma separated list of paths to `includeBuild()`
7685
# This is useful for local development of smithy-kotlin in particular
7786
compositeProjects=../smithy-kotlin
87+
```
88+
89+
#### Generating Specific Services Based on Name or Protocol
7890

79-
# comma separated list of services to generate from codegen/sdk/aws-models. When not specified all services are generated
80-
# service names match the filenames in the models directory `service.VERSION.json`
81-
aws.services=lambda
91+
A comma separated list of services to include or exclude for generation from codegen/sdk/aws-models may
92+
be specified with the `aws.services` property. A list of protocols of services to generate may be specified
93+
with the `aws.protocols` property.
94+
95+
Included services require a '+' character prefix and excluded services require a '-' character.
96+
If any items are specified for inclusion, only specified included members will be generated. If no items
97+
are specified for inclusion, all members not excluded will be generated.
98+
When unspecified all services found in the directory specified by the `modelsDir` property are generated.
99+
Service names match the filenames in the models directory `service.VERSION.json`.
100+
101+
Some example entries for `local.properties`:
102+
```ini
103+
# Generate only AWS Lambda:
104+
aws.services=+lambda
82105
```
83106

107+
```ini
108+
# Generate all services but AWS location and AWS DynamoDB:
109+
aws.services=-location,-dynamodb
110+
```
84111

112+
```ini
113+
# Generate all services except those using the restJson1 protocol:
114+
aws.protocols=-restJson1
115+
```

codegen/sdk/build.gradle.kts

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,21 +110,36 @@ val sdkPackageNamePrefix = "aws.sdk.kotlin.services."
110110
// Returns an AwsService model for every JSON file found in in directory defined by property `modelsDirProp`
111111
fun discoverServices(): List<AwsService> {
112112
val modelsDir: String by project
113-
val serviceIncludeList = getProperty("aws.services")?.split(",")?.map { it.trim() }
113+
val serviceMembership = parseMembership(getProperty("aws.services"))
114+
val protocolMembership = parseMembership(getProperty("aws.protocols"))
114115

115116
return fileTree(project.file(modelsDir))
116-
.filter {
117-
val includeModel = serviceIncludeList?.contains(it.name.split(".").first()) ?: true
118-
if (!includeModel) {
119-
logger.info("skipping ${it.absolutePath}, name not included in aws.services")
117+
.filter { file ->
118+
val svcName = file.name.split(".").first()
119+
val include = serviceMembership.isMember(svcName)
120+
121+
if (!include) {
122+
logger.info("skipping ${file.absolutePath}, $svcName not a member of $serviceMembership")
120123
}
121-
includeModel
124+
include
122125
}
123126
.map { file ->
124127
val model = Model.assembler().addImport(file.absolutePath).assemble().result.get()
125128
val services: List<ServiceShape> = model.shapes(ServiceShape::class.java).sorted().toList()
126129
require(services.size == 1) { "Expected one service per aws model, but found ${services.size} in ${file.absolutePath}: ${services.map { it.id }}" }
127130
val service = services.first()
131+
file to service
132+
}
133+
.filter { (file, service) ->
134+
val protocol = service.protocol()
135+
val include = protocolMembership.isMember(protocol)
136+
137+
if (!include) {
138+
logger.info("skipping ${file.absolutePath}, $protocol not a member of $protocolMembership")
139+
}
140+
include
141+
}
142+
.map { (file, service) ->
128143
val serviceApi = service.getTrait(software.amazon.smithy.aws.traits.ServiceTrait::class.java).orNull()
129144
?: error { "Expected aws.api#service trait attached to model ${file.absolutePath}" }
130145
val (name, version, _) = file.name.split(".")
@@ -147,6 +162,46 @@ fun discoverServices(): List<AwsService> {
147162
}
148163
}
149164

165+
// Returns the trait name of the protocol of the service
166+
fun ServiceShape.protocol(): String =
167+
listOf(
168+
"aws.protocols#awsJson1_0",
169+
"aws.protocols#awsJson1_1",
170+
"aws.protocols#awsQuery",
171+
"aws.protocols#ec2Query",
172+
"aws.protocols#ec2QueryName",
173+
"aws.protocols#restJson1",
174+
"aws.protocols#restXml"
175+
).first { protocol -> findTrait(protocol).isPresent }.split("#")[1]
176+
177+
// Class and functions for service and protocol membership for SDK generation
178+
data class Membership(val inclusions: Set<String> = emptySet(), val exclusions: Set<String> = emptySet())
179+
fun Membership.isMember(member: String): Boolean = when {
180+
exclusions.contains(member) -> false
181+
inclusions.contains(member) -> true
182+
inclusions.isEmpty() -> true
183+
else -> false
184+
}
185+
fun parseMembership(rawList: String?): Membership {
186+
if (rawList == null) return Membership()
187+
188+
val inclusions = mutableSetOf<String>()
189+
val exclusions = mutableSetOf<String>()
190+
191+
rawList.split(",").map { it.trim() }.forEach { item ->
192+
when {
193+
item.startsWith('-') -> exclusions.add(item.substring(1))
194+
item.startsWith('+') -> inclusions.add(item.substring(1))
195+
else -> error("Must specify inclusion (+) or exclusion (-) prefix character to $item.")
196+
}
197+
}
198+
199+
val conflictingMembers = inclusions.intersect(exclusions)
200+
require(conflictingMembers.isEmpty()) { "$conflictingMembers specified both for inclusion and exclusion in $rawList" }
201+
202+
return Membership(inclusions, exclusions)
203+
}
204+
150205
fun <T> java.util.Optional<T>.orNull(): T? = this.orElse(null)
151206

152207
/**

0 commit comments

Comments
 (0)