1// Copyright 2019 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package rust 16 17import ( 18 "path/filepath" 19 "strings" 20 21 "github.com/google/blueprint" 22 23 "android/soong/android" 24 "android/soong/cc" 25 "android/soong/rust/config" 26) 27 28var ( 29 _ = pctx.SourcePathVariable("rustcCmd", "${config.RustBin}/rustc") 30 rustc = pctx.AndroidStaticRule("rustc", 31 blueprint.RuleParams{ 32 Command: "$envVars $rustcCmd " + 33 "-C linker=${config.RustLinker} " + 34 "-C link-args=\"${crtBegin} ${earlyLinkFlags} ${linkFlags} ${crtEnd}\" " + 35 "--emit link -o $out --emit dep-info=$out.d.raw $in ${libFlags} $rustcFlags" + 36 " && grep ^$out: $out.d.raw > $out.d", 37 CommandDeps: []string{"$rustcCmd"}, 38 // Rustc deps-info writes out make compatible dep files: https://github.com/rust-lang/rust/issues/7633 39 // Rustc emits unneeded dependency lines for the .d and input .rs files. 40 // Those extra lines cause ninja warning: 41 // "warning: depfile has multiple output paths" 42 // For ninja, we keep/grep only the dependency rule for the rust $out file. 43 Deps: blueprint.DepsGCC, 44 Depfile: "$out.d", 45 }, 46 "rustcFlags", "earlyLinkFlags", "linkFlags", "libFlags", "crtBegin", "crtEnd", "envVars") 47 48 _ = pctx.SourcePathVariable("rustdocCmd", "${config.RustBin}/rustdoc") 49 rustdoc = pctx.AndroidStaticRule("rustdoc", 50 blueprint.RuleParams{ 51 Command: "$envVars $rustdocCmd $rustdocFlags $in -o $outDir && " + 52 "touch $out", 53 CommandDeps: []string{"$rustdocCmd"}, 54 }, 55 "rustdocFlags", "outDir", "envVars") 56 57 _ = pctx.SourcePathVariable("clippyCmd", "${config.RustBin}/clippy-driver") 58 clippyDriver = pctx.AndroidStaticRule("clippy", 59 blueprint.RuleParams{ 60 Command: "$envVars $clippyCmd " + 61 // Because clippy-driver uses rustc as backend, we need to have some output even during the linting. 62 // Use the metadata output as it has the smallest footprint. 63 "--emit metadata -o $out --emit dep-info=$out.d.raw $in ${libFlags} " + 64 "$rustcFlags $clippyFlags" + 65 " && grep ^$out: $out.d.raw > $out.d", 66 CommandDeps: []string{"$clippyCmd"}, 67 Deps: blueprint.DepsGCC, 68 Depfile: "$out.d", 69 }, 70 "rustcFlags", "libFlags", "clippyFlags", "envVars") 71 72 zip = pctx.AndroidStaticRule("zip", 73 blueprint.RuleParams{ 74 Command: "cat $out.rsp | tr ' ' '\\n' | tr -d \\' | sort -u > ${out}.tmp && ${SoongZipCmd} -o ${out} -C $$OUT_DIR -l ${out}.tmp", 75 CommandDeps: []string{"${SoongZipCmd}"}, 76 Rspfile: "$out.rsp", 77 RspfileContent: "$in", 78 }) 79 80 cp = pctx.AndroidStaticRule("cp", 81 blueprint.RuleParams{ 82 Command: "cp `cat $outDir.rsp` $outDir", 83 Rspfile: "${outDir}.rsp", 84 RspfileContent: "$in", 85 }, 86 "outDir") 87 88 // Cross-referencing: 89 _ = pctx.SourcePathVariable("rustExtractor", 90 "prebuilts/build-tools/${config.HostPrebuiltTag}/bin/rust_extractor") 91 _ = pctx.VariableFunc("kytheCorpus", 92 func(ctx android.PackageVarContext) string { return ctx.Config().XrefCorpusName() }) 93 _ = pctx.VariableFunc("kytheCuEncoding", 94 func(ctx android.PackageVarContext) string { return ctx.Config().XrefCuEncoding() }) 95 _ = pctx.SourcePathVariable("kytheVnames", "build/soong/vnames.json") 96 kytheExtract = pctx.AndroidStaticRule("kythe", 97 blueprint.RuleParams{ 98 Command: `KYTHE_CORPUS=${kytheCorpus} ` + 99 `KYTHE_OUTPUT_FILE=$out ` + 100 `KYTHE_VNAMES=$kytheVnames ` + 101 `KYTHE_KZIP_ENCODING=${kytheCuEncoding} ` + 102 `KYTHE_CANONICALIZE_VNAME_PATHS=prefer-relative ` + 103 `$rustExtractor $envVars ` + 104 `$rustcCmd ` + 105 `-C linker=${config.RustLinker} ` + 106 `-C link-args="${crtBegin} ${linkFlags} ${crtEnd}" ` + 107 `$in ${libFlags} $rustcFlags`, 108 CommandDeps: []string{"$rustExtractor", "$kytheVnames"}, 109 Rspfile: "${out}.rsp", 110 RspfileContent: "$in", 111 }, 112 "rustcFlags", "linkFlags", "libFlags", "crtBegin", "crtEnd", "envVars") 113) 114 115type buildOutput struct { 116 outputFile android.Path 117 kytheFile android.Path 118} 119 120func init() { 121 pctx.HostBinToolVariable("SoongZipCmd", "soong_zip") 122 cc.TransformRlibstoStaticlib = TransformRlibstoStaticlib 123} 124 125type transformProperties struct { 126 crateName string 127 targetTriple string 128 is64Bit bool 129 bootstrap bool 130 inRecovery bool 131 inRamdisk bool 132 inVendorRamdisk bool 133 cargoOutDir android.OptionalPath 134 synthetic bool 135 crateType string 136} 137 138// Populates a standard transformProperties struct for Rust modules 139func getTransformProperties(ctx ModuleContext, crateType string) transformProperties { 140 module := ctx.RustModule() 141 return transformProperties{ 142 crateName: module.CrateName(), 143 is64Bit: ctx.toolchain().Is64Bit(), 144 targetTriple: ctx.toolchain().RustTriple(), 145 bootstrap: module.Bootstrap(), 146 inRecovery: module.InRecovery(), 147 inRamdisk: module.InRamdisk(), 148 inVendorRamdisk: module.InVendorRamdisk(), 149 cargoOutDir: module.compiler.cargoOutDir(), 150 151 // crateType indicates what type of crate to build 152 crateType: crateType, 153 154 // synthetic indicates whether this is an actual Rust module or not 155 synthetic: false, 156 } 157} 158 159func TransformSrcToBinary(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, 160 outputFile android.WritablePath) buildOutput { 161 if ctx.RustModule().compiler.Thinlto() { 162 flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin") 163 } 164 165 return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "bin")) 166} 167 168func TransformSrctoRlib(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, 169 outputFile android.WritablePath) buildOutput { 170 return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "rlib")) 171} 172 173func TransformRlibstoStaticlib(ctx android.ModuleContext, mainSrc android.Path, deps []cc.RustRlibDep, 174 outputFile android.WritablePath) android.Path { 175 176 var rustPathDeps PathDeps 177 var rustFlags Flags 178 179 for _, rlibDep := range deps { 180 rustPathDeps.RLibs = append(rustPathDeps.RLibs, RustLibrary{Path: rlibDep.LibPath, CrateName: rlibDep.CrateName}) 181 rustPathDeps.linkDirs = append(rustPathDeps.linkDirs, rlibDep.LinkDirs...) 182 } 183 184 ccModule := ctx.(cc.ModuleContext).Module().(*cc.Module) 185 toolchain := config.FindToolchain(ctx.Os(), ctx.Arch()) 186 t := transformProperties{ 187 // Crate name can be a predefined value as this is a staticlib and 188 // it does not need to be unique. The crate name is used for name 189 // mangling, but it is mixed with the metadata for that purpose, which we 190 // already set to the module name. 191 crateName: "generated_rust_staticlib", 192 is64Bit: toolchain.Is64Bit(), 193 targetTriple: toolchain.RustTriple(), 194 bootstrap: ccModule.Bootstrap(), 195 inRecovery: ccModule.InRecovery(), 196 inRamdisk: ccModule.InRamdisk(), 197 inVendorRamdisk: ccModule.InVendorRamdisk(), 198 199 // crateType indicates what type of crate to build 200 crateType: "staticlib", 201 202 // synthetic indicates whether this is an actual Rust module or not 203 synthetic: true, 204 } 205 206 rustFlags = CommonDefaultFlags(ctx, toolchain, rustFlags) 207 rustFlags = CommonLibraryCompilerFlags(ctx, rustFlags) 208 rustFlags.GlobalRustFlags = append(rustFlags.GlobalRustFlags, "-C lto=thin") 209 210 rustFlags.EmitXrefs = false 211 212 return transformSrctoCrate(ctx, mainSrc, rustPathDeps, rustFlags, outputFile, t).outputFile 213} 214 215func TransformSrctoDylib(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, 216 outputFile android.WritablePath) buildOutput { 217 if ctx.RustModule().compiler.Thinlto() { 218 flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin") 219 } 220 221 return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "dylib")) 222} 223 224func TransformSrctoStatic(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, 225 outputFile android.WritablePath) buildOutput { 226 if ctx.RustModule().compiler.Thinlto() { 227 flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin") 228 } 229 230 return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "staticlib")) 231} 232 233func TransformSrctoShared(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags, 234 outputFile android.WritablePath) buildOutput { 235 if ctx.RustModule().compiler.Thinlto() { 236 flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin") 237 } 238 239 return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "cdylib")) 240} 241 242func TransformSrctoProcMacro(ctx ModuleContext, mainSrc android.Path, deps PathDeps, 243 flags Flags, outputFile android.WritablePath) buildOutput { 244 return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "proc-macro")) 245} 246 247func rustLibsToPaths(libs RustLibraries) android.Paths { 248 var paths android.Paths 249 for _, lib := range libs { 250 paths = append(paths, lib.Path) 251 } 252 return paths 253} 254 255func makeLibFlags(deps PathDeps) []string { 256 var libFlags []string 257 258 // Collect library/crate flags 259 for _, lib := range deps.RLibs { 260 libFlags = append(libFlags, "--extern "+lib.CrateName+"="+lib.Path.String()) 261 } 262 for _, lib := range deps.DyLibs { 263 libFlags = append(libFlags, "--extern "+lib.CrateName+"="+lib.Path.String()) 264 } 265 for _, proc_macro := range deps.ProcMacros { 266 libFlags = append(libFlags, "--extern "+proc_macro.CrateName+"="+proc_macro.Path.String()) 267 } 268 269 for _, path := range deps.linkDirs { 270 libFlags = append(libFlags, "-L "+path) 271 } 272 273 return libFlags 274} 275 276func rustEnvVars(ctx android.ModuleContext, deps PathDeps, crateName string, cargoOutDir android.OptionalPath) []string { 277 var envVars []string 278 279 // libstd requires a specific environment variable to be set. This is 280 // not officially documented and may be removed in the future. See 281 // https://github.com/rust-lang/rust/blob/master/library/std/src/env.rs#L866. 282 if crateName == "std" { 283 envVars = append(envVars, "STD_ENV_ARCH="+config.StdEnvArch[ctx.Arch().ArchType]) 284 } 285 286 if len(deps.SrcDeps) > 0 && cargoOutDir.Valid() { 287 moduleGenDir := cargoOutDir 288 // We must calculate an absolute path for OUT_DIR since Rust's include! macro (which normally consumes this) 289 // assumes that paths are relative to the source file. 290 var outDirPrefix string 291 if !filepath.IsAbs(moduleGenDir.String()) { 292 // If OUT_DIR is not absolute, we use $$PWD to generate an absolute path (os.Getwd() returns '/') 293 outDirPrefix = "$$PWD/" 294 } else { 295 // If OUT_DIR is absolute, then moduleGenDir will be an absolute path, so we don't need to set this to anything. 296 outDirPrefix = "" 297 } 298 envVars = append(envVars, "OUT_DIR="+filepath.Join(outDirPrefix, moduleGenDir.String())) 299 } else { 300 // TODO(pcc): Change this to "OUT_DIR=" after fixing crates to not rely on this value. 301 envVars = append(envVars, "OUT_DIR=out") 302 } 303 304 envVars = append(envVars, "ANDROID_RUST_VERSION="+config.GetRustVersion(ctx)) 305 306 if rustMod, ok := ctx.Module().(*Module); ok && rustMod.compiler.cargoEnvCompat() { 307 // We only emulate cargo environment variables for 3p code, which is only ever built 308 // by defining a Rust module, so we only need to set these for true Rust modules. 309 if bin, ok := rustMod.compiler.(*binaryDecorator); ok { 310 envVars = append(envVars, "CARGO_BIN_NAME="+bin.getStem(ctx)) 311 } 312 envVars = append(envVars, "CARGO_CRATE_NAME="+crateName) 313 envVars = append(envVars, "CARGO_PKG_NAME="+crateName) 314 pkgVersion := rustMod.compiler.cargoPkgVersion() 315 if pkgVersion != "" { 316 envVars = append(envVars, "CARGO_PKG_VERSION="+pkgVersion) 317 318 // Ensure the version is in the form of "x.y.z" (approximately semver compliant). 319 // 320 // For our purposes, we don't care to enforce that these are integers since they may 321 // include other characters at times (e.g. sometimes the patch version is more than an integer). 322 if strings.Count(pkgVersion, ".") == 2 { 323 var semver_parts = strings.Split(pkgVersion, ".") 324 envVars = append(envVars, "CARGO_PKG_VERSION_MAJOR="+semver_parts[0]) 325 envVars = append(envVars, "CARGO_PKG_VERSION_MINOR="+semver_parts[1]) 326 envVars = append(envVars, "CARGO_PKG_VERSION_PATCH="+semver_parts[2]) 327 } 328 } 329 } 330 331 if ctx.Darwin() { 332 envVars = append(envVars, "ANDROID_RUST_DARWIN=true") 333 } 334 335 return envVars 336} 337 338func transformSrctoCrate(ctx android.ModuleContext, main android.Path, deps PathDeps, flags Flags, 339 outputFile android.WritablePath, t transformProperties) buildOutput { 340 341 var inputs android.Paths 342 var implicits android.Paths 343 var orderOnly android.Paths 344 var output buildOutput 345 var rustcFlags, linkFlags []string 346 var earlyLinkFlags string 347 348 output.outputFile = outputFile 349 350 envVars := rustEnvVars(ctx, deps, t.crateName, t.cargoOutDir) 351 352 inputs = append(inputs, main) 353 354 // Collect rustc flags 355 rustcFlags = append(rustcFlags, flags.GlobalRustFlags...) 356 rustcFlags = append(rustcFlags, flags.RustFlags...) 357 rustcFlags = append(rustcFlags, "--crate-type="+t.crateType) 358 if t.crateName != "" { 359 rustcFlags = append(rustcFlags, "--crate-name="+t.crateName) 360 } 361 if t.targetTriple != "" { 362 rustcFlags = append(rustcFlags, "--target="+t.targetTriple) 363 linkFlags = append(linkFlags, "-target "+t.targetTriple) 364 } 365 366 // Suppress an implicit sysroot 367 rustcFlags = append(rustcFlags, "--sysroot=/dev/null") 368 369 // Enable incremental compilation if requested by user 370 if ctx.Config().IsEnvTrue("SOONG_RUSTC_INCREMENTAL") { 371 incrementalPath := android.PathForOutput(ctx, "rustc").String() 372 373 rustcFlags = append(rustcFlags, "-C incremental="+incrementalPath) 374 } else { 375 rustcFlags = append(rustcFlags, "-C codegen-units=1") 376 } 377 378 // Disallow experimental features 379 modulePath := ctx.ModuleDir() 380 if !(android.IsThirdPartyPath(modulePath) || strings.HasPrefix(modulePath, "prebuilts")) { 381 rustcFlags = append(rustcFlags, "-Zallow-features=\"\"") 382 } 383 384 // Collect linker flags 385 if !ctx.Darwin() { 386 earlyLinkFlags = "-Wl,--as-needed" 387 } 388 389 linkFlags = append(linkFlags, flags.GlobalLinkFlags...) 390 linkFlags = append(linkFlags, flags.LinkFlags...) 391 392 // Check if this module needs to use the bootstrap linker 393 if t.bootstrap && !t.inRecovery && !t.inRamdisk && !t.inVendorRamdisk { 394 dynamicLinker := "-Wl,-dynamic-linker,/system/bin/bootstrap/linker" 395 if t.is64Bit { 396 dynamicLinker += "64" 397 } 398 linkFlags = append(linkFlags, dynamicLinker) 399 } 400 401 libFlags := makeLibFlags(deps) 402 403 // Collect dependencies 404 implicits = append(implicits, rustLibsToPaths(deps.RLibs)...) 405 implicits = append(implicits, rustLibsToPaths(deps.DyLibs)...) 406 implicits = append(implicits, rustLibsToPaths(deps.ProcMacros)...) 407 implicits = append(implicits, deps.StaticLibs...) 408 implicits = append(implicits, deps.SharedLibDeps...) 409 implicits = append(implicits, deps.srcProviderFiles...) 410 implicits = append(implicits, deps.AfdoProfiles...) 411 412 implicits = append(implicits, deps.CrtBegin...) 413 implicits = append(implicits, deps.CrtEnd...) 414 415 orderOnly = append(orderOnly, deps.SharedLibs...) 416 417 if !t.synthetic { 418 // Only worry about OUT_DIR for actual Rust modules. 419 // Libraries built from cc use generated source, and do not utilize OUT_DIR. 420 if len(deps.SrcDeps) > 0 { 421 var outputs android.WritablePaths 422 423 for _, genSrc := range deps.SrcDeps { 424 if android.SuffixInList(outputs.Strings(), genSubDir+genSrc.Base()) { 425 ctx.PropertyErrorf("srcs", 426 "multiple source providers generate the same filename output: "+genSrc.Base()) 427 } 428 outputs = append(outputs, android.PathForModuleOut(ctx, genSubDir+genSrc.Base())) 429 } 430 431 ctx.Build(pctx, android.BuildParams{ 432 Rule: cp, 433 Description: "cp " + t.cargoOutDir.Path().Rel(), 434 Outputs: outputs, 435 Inputs: deps.SrcDeps, 436 Args: map[string]string{ 437 "outDir": t.cargoOutDir.String(), 438 }, 439 }) 440 implicits = append(implicits, outputs.Paths()...) 441 } 442 } 443 444 if !t.synthetic { 445 // Only worry about clippy for actual Rust modules. 446 // Libraries built from cc use generated source, and don't need to run clippy. 447 if flags.Clippy { 448 clippyFile := android.PathForModuleOut(ctx, outputFile.Base()+".clippy") 449 ctx.Build(pctx, android.BuildParams{ 450 Rule: clippyDriver, 451 Description: "clippy " + main.Rel(), 452 Output: clippyFile, 453 ImplicitOutputs: nil, 454 Inputs: inputs, 455 Implicits: implicits, 456 OrderOnly: orderOnly, 457 Args: map[string]string{ 458 "rustcFlags": strings.Join(rustcFlags, " "), 459 "libFlags": strings.Join(libFlags, " "), 460 "clippyFlags": strings.Join(flags.ClippyFlags, " "), 461 "envVars": strings.Join(envVars, " "), 462 }, 463 }) 464 // Declare the clippy build as an implicit dependency of the original crate. 465 implicits = append(implicits, clippyFile) 466 } 467 } 468 469 ctx.Build(pctx, android.BuildParams{ 470 Rule: rustc, 471 Description: "rustc " + main.Rel(), 472 Output: outputFile, 473 Inputs: inputs, 474 Implicits: implicits, 475 OrderOnly: orderOnly, 476 Args: map[string]string{ 477 "rustcFlags": strings.Join(rustcFlags, " "), 478 "earlyLinkFlags": earlyLinkFlags, 479 "linkFlags": strings.Join(linkFlags, " "), 480 "libFlags": strings.Join(libFlags, " "), 481 "crtBegin": strings.Join(deps.CrtBegin.Strings(), " "), 482 "crtEnd": strings.Join(deps.CrtEnd.Strings(), " "), 483 "envVars": strings.Join(envVars, " "), 484 }, 485 }) 486 487 if !t.synthetic { 488 // Only emit xrefs for true Rust modules. 489 if flags.EmitXrefs { 490 kytheFile := android.PathForModuleOut(ctx, outputFile.Base()+".kzip") 491 ctx.Build(pctx, android.BuildParams{ 492 Rule: kytheExtract, 493 Description: "Xref Rust extractor " + main.Rel(), 494 Output: kytheFile, 495 Inputs: inputs, 496 Implicits: implicits, 497 OrderOnly: orderOnly, 498 Args: map[string]string{ 499 "rustcFlags": strings.Join(rustcFlags, " "), 500 "linkFlags": strings.Join(linkFlags, " "), 501 "libFlags": strings.Join(libFlags, " "), 502 "crtBegin": strings.Join(deps.CrtBegin.Strings(), " "), 503 "crtEnd": strings.Join(deps.CrtEnd.Strings(), " "), 504 "envVars": strings.Join(envVars, " "), 505 }, 506 }) 507 output.kytheFile = kytheFile 508 } 509 } 510 return output 511} 512 513func Rustdoc(ctx ModuleContext, main android.Path, deps PathDeps, 514 flags Flags) android.ModuleOutPath { 515 516 rustdocFlags := append([]string{}, flags.RustdocFlags...) 517 rustdocFlags = append(rustdocFlags, "--sysroot=/dev/null") 518 519 // Build an index for all our crates. -Z unstable options is required to use 520 // this flag. 521 rustdocFlags = append(rustdocFlags, "-Z", "unstable-options", "--enable-index-page") 522 523 targetTriple := ctx.toolchain().RustTriple() 524 525 // Collect rustc flags 526 if targetTriple != "" { 527 rustdocFlags = append(rustdocFlags, "--target="+targetTriple) 528 } 529 530 crateName := ctx.RustModule().CrateName() 531 rustdocFlags = append(rustdocFlags, "--crate-name "+crateName) 532 533 rustdocFlags = append(rustdocFlags, makeLibFlags(deps)...) 534 docTimestampFile := android.PathForModuleOut(ctx, "rustdoc.timestamp") 535 536 // Silence warnings about renamed lints for third-party crates 537 modulePath := ctx.ModuleDir() 538 if android.IsThirdPartyPath(modulePath) { 539 rustdocFlags = append(rustdocFlags, " -A warnings") 540 } 541 542 // Yes, the same out directory is used simultaneously by all rustdoc builds. 543 // This is what cargo does. The docs for individual crates get generated to 544 // a subdirectory named for the crate, and rustdoc synchronizes writes to 545 // shared pieces like the index and search data itself. 546 // https://github.com/rust-lang/rust/blob/master/src/librustdoc/html/render/write_shared.rs#L144-L146 547 docDir := android.PathForOutput(ctx, "rustdoc") 548 549 ctx.Build(pctx, android.BuildParams{ 550 Rule: rustdoc, 551 Description: "rustdoc " + main.Rel(), 552 Output: docTimestampFile, 553 Input: main, 554 Implicit: ctx.RustModule().UnstrippedOutputFile(), 555 Args: map[string]string{ 556 "rustdocFlags": strings.Join(rustdocFlags, " "), 557 "outDir": docDir.String(), 558 "envVars": strings.Join(rustEnvVars(ctx, deps, crateName, ctx.RustModule().compiler.cargoOutDir()), " "), 559 }, 560 }) 561 562 return docTimestampFile 563} 564