Avoid unnecessary Cargo crate rebuilds (#1422)

Avoid unnecessary Cargo crate rebuilds

This commit modifies the Gradle buildscripts to avoid unnecessary Cargo
rebuilds of the generated crates, decreasing development iteration
cycles.

Prior to this commit, if you ran a crate-generating command _twice_ on
any of the `codegen-test`, `codegen-server-test`, `sdk-codegen-test`,
and `codegen-server-test:python` subprojects (without making any changes
to the codegen code), both invocations would take the same time: Cargo
would recompile the crate and its dependencies, even when the generated
crate is identical. This is because Gradle deletes everything under
`buildDir` when running the `generateSmithyBuild` task:

```
> Task :codegen-server-test:smithyBuildJar
Deleting stale output file: /local/home/davidpz/workplace/smithy-ws/src/SmithyRsSource/codegen-server-test/build/smithyprojections/codegen-server-test
```

So the files get recreated each time.

While developing, it is likely that only a small number of the generated
crate files are modified across rebuilds. [Cargo uses
`mtime`](https://github.com/rust-lang/cargo/issues/6529) (among other
factors) to determine whether it needs to recompile a unit. Indeed,
running with `CARGO_LOG=cargo::core::compiler::fingerprint=trace` yields
`err: current filesystem status shows we're outdated`. This commit adds
a Gradle task that compares the hashes of the newly generated files with
the (previously cached) old ones, and restores their `mtime`s if the
hashes coincide.

Another issue was causing unnecessary crate rebuilds. Prior to this
commit, we were sending `RUSTFLAGS=-D warnings` when invoking Cargo.
However, a common thing to do after generating a crate is to open its
contents in an editor. The editor's `rust-analyzer` would compile the
crate and its dependencies without the `RUSTFLAGS` we had used earlier.
The next time you rebuilt the crate, Cargo would claim `err: RUSTFLAGS
has changed: previously [], now ["-D", "warnings"]` and recompile
everything again. This commit refactors the Gradle tasks so as to not
send these flags when invoking Cargo, instead generating a
`.cargo/config.toml` containing these flags. This way, `rust-analyzer`
also picks them up and does not need to recompile the crates.

With both patches, Cargo avoids unnecessary crate rebuilds. All in all,
the second invocation of a `./gradlew --info -P modules='simple' -P
cargoCommands='test' codegen-server-test:build` command now takes 15
seconds less than the first invocation on my `c5.4xlarge` machine; Cargo
does not need to do _any_ work on the second invocation.

This commit also refactors the `build.gradle.kts` files of the `sdk`,
`sdk-codegen-test`, `codegen-test`, `codegen-server-test`, and
`codegen-server-test:python` subprojects to make them DRYer and more
consistent. The last 4 subprojects' buildscripts are now much shorter,
with all the common logic having been moved to `CodegenTestCommon.kt`.
Note that we have made the last 4 subprojects' `cargo check` and `cargo
doc` invocations use the same set of flags than in the `sdk` subproject
for consistency.

Closes #1378.
Closes #1412.
This commit is contained in:
david-perez 2022-06-16 00:06:25 +02:00 committed by GitHub
parent 04eeb4ff13
commit 40f22207ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 181 additions and 277 deletions

View File

@ -8,13 +8,14 @@ extra["moduleName"] = "software.amazon.smithy.kotlin.codegen.test"
tasks["jar"].enabled = false
plugins {
id("software.amazon.smithy").version("0.5.3")
}
plugins { id("software.amazon.smithy").version("0.5.3") }
val smithyVersion: String by project
val defaultRustFlags: String by project
val defaultRustDocFlags: String by project
val properties = PropertyRetriever(rootProject, project)
val pluginName = "rust-codegen"
val workingDirUnderBuildDir = "smithyprojections/sdk-codegen-test/"
dependencies {
implementation(project(":aws:sdk-codegen"))
@ -23,94 +24,19 @@ dependencies {
implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion")
}
data class CodegenTest(val service: String, val module: String, val extraConfig: String? = null)
val CodegenTests = listOf(
val allCodegenTests = listOf(
CodegenTest("com.amazonaws.apigateway#BackplaneControlService", "apigateway")
)
fun generateSmithyBuild(tests: List<CodegenTest>): String {
val projections = tests.joinToString(",\n") {
"""
"${it.module}": {
"plugins": {
"rust-codegen": {
"runtimeConfig": {
"relativePath": "${rootProject.projectDir.absolutePath}/rust-runtime"
},
"service": "${it.service}",
"module": "${it.module}",
"moduleAuthors": ["protocoltest@example.com"],
"moduleVersion": "0.0.1"
${it.extraConfig ?: ""}
}
}
}
""".trimIndent()
}
return """
{
"version": "1.0",
"projections": { $projections }
}
"""
}
task("generateSmithyBuild") {
description = "generate smithy-build.json"
doFirst {
buildDir.resolve("smithy-build.json").writeText(generateSmithyBuild(CodegenTests))
}
}
fun generateCargoWorkspace(tests: List<CodegenTest>): String {
return """
[workspace]
members = [
${tests.joinToString(",") { "\"${it.module}/rust-codegen\"" }}
]
""".trimIndent()
}
task("generateCargoWorkspace") {
description = "generate Cargo.toml workspace file"
doFirst {
buildDir.resolve("smithyprojections/sdk-codegen-test/Cargo.toml").writeText(generateCargoWorkspace(CodegenTests))
}
}
project.registerGenerateSmithyBuildTask(rootProject, pluginName, allCodegenTests)
project.registerGenerateCargoWorkspaceTask(rootProject, pluginName, allCodegenTests, workingDirUnderBuildDir)
tasks["smithyBuildJar"].dependsOn("generateSmithyBuild")
tasks["assemble"].finalizedBy("generateCargoWorkspace")
tasks.register<Exec>("cargoCheck") {
workingDir("build/smithyprojections/sdk-codegen-test/")
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "check")
dependsOn("assemble")
}
project.registerModifyMtimeTask()
project.registerCargoCommandsTasks(buildDir.resolve(workingDirUnderBuildDir), defaultRustDocFlags)
tasks.register<Exec>("cargoTest") {
workingDir("build/smithyprojections/sdk-codegen-test/")
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "test")
dependsOn("assemble")
}
tasks["test"].finalizedBy(cargoCommands(properties).map { it.toString })
tasks.register<Exec>("cargoDocs") {
workingDir("build/smithyprojections/sdk-codegen-test/")
environment("RUSTDOCFLAGS", defaultRustDocFlags)
commandLine("cargo", "doc", "--no-deps")
dependsOn("assemble")
}
tasks.register<Exec>("cargoClippy") {
workingDir("build/smithyprojections/sdk-codegen-test/")
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "clippy")
dependsOn("assemble")
}
tasks["test"].finalizedBy("cargoCheck", "cargoClippy", "cargoTest", "cargoDocs")
tasks["clean"].doFirst {
delete("smithy-build.json")
}
tasks["clean"].doFirst { delete("smithy-build.json") }

View File

@ -23,7 +23,6 @@ configure<software.amazon.smithy.gradle.SmithyExtension> {
}
val smithyVersion: String by project
val defaultRustFlags: String by project
val defaultRustDocFlags: String by project
val properties = PropertyRetriever(rootProject, project)
@ -393,35 +392,10 @@ tasks["assemble"].apply {
finalizedBy("finalizeSdk")
}
tasks.register<Exec>("cargoCheck") {
workingDir(outputDir)
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "check", "--lib", "--tests", "--benches")
dependsOn("assemble")
}
project.registerCargoCommandsTasks(outputDir, defaultRustDocFlags)
project.registerGenerateCargoConfigTomlTask(outputDir)
tasks.register<Exec>("cargoTest") {
workingDir(outputDir)
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "test")
dependsOn("assemble")
}
tasks.register<Exec>("cargoDocs") {
workingDir(outputDir)
environment("RUSTDOCFLAGS", defaultRustDocFlags)
commandLine("cargo", "doc", "--no-deps", "--document-private-items")
dependsOn("assemble")
}
tasks.register<Exec>("cargoClippy") {
workingDir(outputDir)
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "clippy")
dependsOn("assemble")
}
tasks["test"].finalizedBy("cargoClippy", "cargoTest", "cargoDocs")
tasks["test"].finalizedBy(Cargo.CLIPPY, Cargo.TEST, Cargo.DOCS)
tasks.register<Delete>("deleteSdk") {
delete = setOf(outputDir)

View File

@ -1,10 +1,14 @@
import java.lang.IllegalArgumentException
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import org.gradle.api.Project
import org.gradle.api.tasks.Exec
import org.gradle.kotlin.dsl.extra
import org.gradle.kotlin.dsl.register
import java.io.File
/**
* This file contains common functionality shared across the buildscripts for the `codegen-test` and `codegen-server-test`
* modules.
@ -12,7 +16,7 @@ import java.lang.IllegalArgumentException
data class CodegenTest(val service: String, val module: String, val extraConfig: String? = null)
fun generateSmithyBuild(projectDir: String, pluginName: String, tests: List<CodegenTest>): String {
private fun generateSmithyBuild(projectDir: String, pluginName: String, tests: List<CodegenTest>): String {
val projections = tests.joinToString(",\n") {
"""
"${it.module}": {
@ -49,7 +53,7 @@ enum class Cargo(val toString: String) {
CLIPPY("cargoClippy");
}
fun generateCargoWorkspace(pluginName: String, tests: List<CodegenTest>) =
private fun generateCargoWorkspace(pluginName: String, tests: List<CodegenTest>) =
"""
[workspace]
members = [
@ -60,7 +64,7 @@ fun generateCargoWorkspace(pluginName: String, tests: List<CodegenTest>) =
/**
* Filter the service integration tests for which to generate Rust crates in [allTests] using the given [properties].
*/
fun codegenTests(properties: PropertyRetriever, allTests: List<CodegenTest>): List<CodegenTest> {
private fun codegenTests(properties: PropertyRetriever, allTests: List<CodegenTest>): List<CodegenTest> {
val modulesOverride = properties.get("modules")?.split(",")?.map { it.trim() }
val ret = if (modulesOverride != null) {
@ -76,6 +80,7 @@ fun codegenTests(properties: PropertyRetriever, allTests: List<CodegenTest>): Li
}
val AllCargoCommands = listOf(Cargo.CHECK, Cargo.TEST, Cargo.CLIPPY, Cargo.DOCS)
/**
* Filter the Cargo commands to be run on the generated Rust crates using the given [properties].
* The list of Cargo commands that is run by default is defined in [AllCargoCommands].
@ -102,3 +107,129 @@ fun cargoCommands(properties: PropertyRetriever): List<Cargo> {
}
return ret
}
fun Project.registerGenerateSmithyBuildTask(
rootProject: Project,
pluginName: String,
allCodegenTests: List<CodegenTest>
) {
val properties = PropertyRetriever(rootProject, this)
this.tasks.register("generateSmithyBuild") {
description = "generate smithy-build.json"
outputs.file(project.projectDir.resolve("smithy-build.json"))
doFirst {
project.projectDir.resolve("smithy-build.json")
.writeText(
generateSmithyBuild(
rootProject.projectDir.absolutePath,
pluginName,
codegenTests(properties, allCodegenTests)
)
)
// If this is a rebuild, cache all the hashes of the generated Rust files. These are later used by the
// `modifyMtime` task.
project.extra["previousBuildHashes"] = project.buildDir.walk()
.filter { it.isFile }
.map {
getChecksumForFile(it) to it.lastModified()
}
.toMap()
}
}
}
fun Project.registerGenerateCargoWorkspaceTask(
rootProject: Project,
pluginName: String,
allCodegenTests: List<CodegenTest>,
workingDirUnderBuildDir: String
) {
val properties = PropertyRetriever(rootProject, this)
project.tasks.register("generateCargoWorkspace") {
description = "generate Cargo.toml workspace file"
doFirst {
project.buildDir.resolve("$workingDirUnderBuildDir/Cargo.toml")
.writeText(generateCargoWorkspace(pluginName, codegenTests(properties, allCodegenTests)))
}
}
}
fun Project.registerGenerateCargoConfigTomlTask(
outputDir: File
) {
this.tasks.register("generateCargoConfigToml") {
description = "generate `.cargo/config.toml`"
doFirst {
outputDir.resolve(".cargo").mkdir()
outputDir.resolve(".cargo/config.toml")
.writeText(
"""
[build]
rustflags = ["--deny", "warnings"]
""".trimIndent()
)
}
}
}
fun Project.registerModifyMtimeTask() {
// Cargo uses `mtime` (among other factors) to determine whether a compilation unit needs a rebuild. While developing,
// it is likely that only a small number of the generated crate files are modified across rebuilds. This task compares
// the hashes of the newly generated files with the (previously cached) old ones, and restores their `mtime`s if the
// hashes coincide.
// Debugging tip: it is useful to run with `CARGO_LOG=cargo::core::compiler::fingerprint=trace` to learn why Cargo
// determines a compilation unit needs a rebuild.
// For more information see https://github.com/awslabs/smithy-rs/issues/1412.
this.tasks.register("modifyMtime") {
description = "modify Rust files' `mtime` if the contents did not change"
dependsOn("generateSmithyBuild")
doFirst {
@Suppress("UNCHECKED_CAST") val previousBuildHashes: Map<String, Long> = project.extra["previousBuildHashes"] as Map<String, Long>
project.buildDir.walk()
.filter { it.isFile }
.map {
getChecksumForFile(it) to it
}
.forEach { (currentHash, currentFile) ->
previousBuildHashes[currentHash]?.also { oldMtime ->
println("Setting `mtime` of $currentFile back to `$oldMtime` because its hash `$currentHash` remained unchanged after a rebuild.")
currentFile.setLastModified(oldMtime)
}
}
}
}
}
fun Project.registerCargoCommandsTasks(
outputDir: File,
defaultRustDocFlags: String
) {
this.tasks.register<Exec>(Cargo.CHECK.toString) {
dependsOn("assemble", "modifyMtime", "generateCargoConfigToml")
workingDir(outputDir)
commandLine("cargo", "check", "--lib", "--tests", "--benches")
}
this.tasks.register<Exec>(Cargo.TEST.toString) {
dependsOn("assemble", "modifyMtime", "generateCargoConfigToml")
workingDir(outputDir)
commandLine("cargo", "test")
}
this.tasks.register<Exec>(Cargo.DOCS.toString) {
dependsOn("assemble", "modifyMtime", "generateCargoConfigToml")
workingDir(outputDir)
environment("RUSTDOCFLAGS", defaultRustDocFlags)
commandLine("cargo", "doc", "--no-deps", "--document-private-items")
}
this.tasks.register<Exec>(Cargo.CLIPPY.toString) {
dependsOn("assemble", "modifyMtime", "generateCargoConfigToml")
workingDir(outputDir)
commandLine("cargo", "clippy")
}
}

View File

@ -0,0 +1,12 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import java.io.File
import java.security.MessageDigest
fun ByteArray.toHex() = joinToString(separator = "") { byte -> "%02x".format(byte) }
fun getChecksumForFile(file: File, digest: MessageDigest = MessageDigest.getInstance("SHA-256")): String =
digest.digest(file.readText().toByteArray()).toHex()

View File

@ -12,7 +12,6 @@ tasks["jar"].enabled = false
plugins { id("software.amazon.smithy").version("0.5.3") }
val smithyVersion: String by project
val defaultRustFlags: String by project
val defaultRustDocFlags: String by project
val properties = PropertyRetriever(rootProject, project)
@ -45,58 +44,15 @@ val allCodegenTests = listOf(
CodegenTest("com.aws.example#PokemonService", "pokemon_service_sdk")
)
tasks.register("generateSmithyBuild") {
description = "generate smithy-build.json"
doFirst {
projectDir.resolve("smithy-build.json")
.writeText(
generateSmithyBuild(
rootProject.projectDir.absolutePath,
pluginName,
codegenTests(properties, allCodegenTests)
)
)
}
}
tasks.register("generateCargoWorkspace") {
description = "generate Cargo.toml workspace file"
doFirst {
buildDir.resolve("$workingDirUnderBuildDir/Cargo.toml")
.writeText(generateCargoWorkspace(pluginName, codegenTests(properties, allCodegenTests)))
}
}
project.registerGenerateSmithyBuildTask(rootProject, pluginName, allCodegenTests)
project.registerGenerateCargoWorkspaceTask(rootProject, pluginName, allCodegenTests, workingDirUnderBuildDir)
project.registerGenerateCargoConfigTomlTask(buildDir.resolve(workingDirUnderBuildDir))
tasks["smithyBuildJar"].dependsOn("generateSmithyBuild")
tasks["assemble"].finalizedBy("generateCargoWorkspace")
tasks["assemble"].finalizedBy("generateCargoWorkspace", "generateCargoConfigToml")
tasks.register<Exec>(Cargo.CHECK.toString) {
workingDir("$buildDir/$workingDirUnderBuildDir")
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "check")
dependsOn("assemble")
}
tasks.register<Exec>(Cargo.TEST.toString) {
workingDir("$buildDir/$workingDirUnderBuildDir")
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "test")
dependsOn("assemble")
}
tasks.register<Exec>(Cargo.DOCS.toString) {
workingDir("$buildDir/$workingDirUnderBuildDir")
environment("RUSTDOCFLAGS", defaultRustDocFlags)
commandLine("cargo", "doc", "--no-deps")
dependsOn("assemble")
}
tasks.register<Exec>(Cargo.CLIPPY.toString) {
workingDir("$buildDir/$workingDirUnderBuildDir")
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "clippy")
dependsOn("assemble")
}
project.registerModifyMtimeTask()
project.registerCargoCommandsTasks(buildDir.resolve(workingDirUnderBuildDir), defaultRustDocFlags)
tasks["test"].finalizedBy(cargoCommands(properties).map { it.toString })

View File

@ -12,7 +12,6 @@ tasks["jar"].enabled = false
plugins { id("software.amazon.smithy") }
val smithyVersion: String by project
val defaultRustFlags: String by project
val defaultRustDocFlags: String by project
val properties = PropertyRetriever(rootProject, project)
@ -42,58 +41,15 @@ val allCodegenTests = listOf(
CodegenTest("com.aws.example#PokemonService", "pokemon_service_sdk")
)
task("generateSmithyBuild") {
description = "generate smithy-build.json"
doFirst {
projectDir.resolve("smithy-build.json")
.writeText(
generateSmithyBuild(
rootProject.projectDir.absolutePath,
pluginName,
codegenTests(properties, allCodegenTests)
)
)
}
}
task("generateCargoWorkspace") {
description = "generate Cargo.toml workspace file"
doFirst {
buildDir.resolve("$workingDirUnderBuildDir/Cargo.toml")
.writeText(generateCargoWorkspace(pluginName, codegenTests(properties, allCodegenTests)))
}
}
project.registerGenerateSmithyBuildTask(rootProject, pluginName, allCodegenTests)
project.registerGenerateCargoWorkspaceTask(rootProject, pluginName, allCodegenTests, workingDirUnderBuildDir)
project.registerGenerateCargoConfigTomlTask(buildDir.resolve(workingDirUnderBuildDir))
tasks["smithyBuildJar"].dependsOn("generateSmithyBuild")
tasks["assemble"].finalizedBy("generateCargoWorkspace")
tasks.register<Exec>(Cargo.CHECK.toString) {
workingDir("$buildDir/$workingDirUnderBuildDir")
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "check")
dependsOn("assemble")
}
tasks.register<Exec>(Cargo.TEST.toString) {
workingDir("$buildDir/$workingDirUnderBuildDir")
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "test")
dependsOn("assemble")
}
tasks.register<Exec>(Cargo.DOCS.toString) {
workingDir("$buildDir/$workingDirUnderBuildDir")
environment("RUSTDOCFLAGS", defaultRustDocFlags)
commandLine("cargo", "doc", "--no-deps")
dependsOn("assemble")
}
tasks.register<Exec>(Cargo.CLIPPY.toString) {
workingDir("$buildDir/$workingDirUnderBuildDir")
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "clippy")
dependsOn("assemble")
}
project.registerModifyMtimeTask()
project.registerCargoCommandsTasks(buildDir.resolve(workingDirUnderBuildDir), defaultRustDocFlags)
tasks["test"].finalizedBy(cargoCommands(properties).map { it.toString })

View File

@ -8,12 +8,9 @@ extra["moduleName"] = "software.amazon.smithy.kotlin.codegen.test"
tasks["jar"].enabled = false
plugins {
id("software.amazon.smithy").version("0.5.3")
}
plugins { id("software.amazon.smithy").version("0.5.3") }
val smithyVersion: String by project
val defaultRustFlags: String by project
val defaultRustDocFlags: String by project
val properties = PropertyRetriever(rootProject, project)
@ -88,61 +85,16 @@ val allCodegenTests = listOf(
CodegenTest("com.aws.example#PokemonService", "pokemon_service_client")
)
tasks.register("generateSmithyBuild") {
description = "generate smithy-build.json"
doFirst {
projectDir.resolve("smithy-build.json")
.writeText(
generateSmithyBuild(
rootProject.projectDir.absolutePath,
pluginName,
codegenTests(properties, allCodegenTests)
)
)
}
}
tasks.register("generateCargoWorkspace") {
description = "generate Cargo.toml workspace file"
doFirst {
buildDir.resolve("$workingDirUnderBuildDir/Cargo.toml")
.writeText(generateCargoWorkspace(pluginName, codegenTests(properties, allCodegenTests)))
}
}
project.registerGenerateSmithyBuildTask(rootProject, pluginName, allCodegenTests)
project.registerGenerateCargoWorkspaceTask(rootProject, pluginName, allCodegenTests, workingDirUnderBuildDir)
project.registerGenerateCargoConfigTomlTask(buildDir.resolve(workingDirUnderBuildDir))
tasks["smithyBuildJar"].dependsOn("generateSmithyBuild")
tasks["assemble"].finalizedBy("generateCargoWorkspace")
tasks.register<Exec>(Cargo.CHECK.toString) {
workingDir("$buildDir/$workingDirUnderBuildDir")
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "check")
dependsOn("assemble")
}
tasks.register<Exec>(Cargo.TEST.toString) {
workingDir("$buildDir/$workingDirUnderBuildDir")
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "test")
dependsOn("assemble")
}
tasks.register<Exec>(Cargo.DOCS.toString) {
workingDir("$buildDir/$workingDirUnderBuildDir")
environment("RUSTDOCFLAGS", defaultRustDocFlags)
commandLine("cargo", "doc", "--no-deps")
dependsOn("assemble")
}
tasks.register<Exec>(Cargo.CLIPPY.toString) {
workingDir("$buildDir/$workingDirUnderBuildDir")
environment("RUSTFLAGS", defaultRustFlags)
commandLine("cargo", "clippy")
dependsOn("assemble")
}
project.registerModifyMtimeTask()
project.registerCargoCommandsTasks(buildDir.resolve(workingDirUnderBuildDir), defaultRustDocFlags)
tasks["test"].finalizedBy(cargoCommands(properties).map { it.toString })
tasks["clean"].doFirst {
delete("smithy-build.json")
}
tasks["clean"].doFirst { delete("smithy-build.json") }

View File

@ -27,9 +27,6 @@ kotlinVersion=1.6.20
ktlintVersion=0.45.2
kotestVersion=5.2.3
# rustc
defaultRustFlags=-D warnings
# TODO(https://github.com/awslabs/smithy-rs/issues/1068): Once doc normalization
# is completed, warnings can be prohibited in rustdoc.
#