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