• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2016 Google Inc. All rights reserved.
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 cc
16
17import (
18	"fmt"
19	"path/filepath"
20	"runtime"
21	"strings"
22	"sync"
23
24	"github.com/google/blueprint"
25	"github.com/google/blueprint/proptools"
26
27	"android/soong/android"
28	"android/soong/bazel"
29	"android/soong/cc/config"
30)
31
32func init() {
33	pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen")
34	pctx.HostBinToolVariable("abidiff", "abidiff")
35	pctx.HostBinToolVariable("abitidy", "abitidy")
36	pctx.HostBinToolVariable("abidw", "abidw")
37}
38
39var (
40	genStubSrc = pctx.AndroidStaticRule("genStubSrc",
41		blueprint.RuleParams{
42			Command: "$ndkStubGenerator --arch $arch --api $apiLevel " +
43				"--api-map $apiMap $flags $in $out",
44			CommandDeps: []string{"$ndkStubGenerator"},
45		}, "arch", "apiLevel", "apiMap", "flags")
46
47	abidw = pctx.AndroidStaticRule("abidw",
48		blueprint.RuleParams{
49			Command: "$abidw --type-id-style hash --no-corpus-path " +
50				"--no-show-locs --no-comp-dir-path -w $symbolList " +
51				"$in --out-file $out",
52			CommandDeps: []string{"$abidw"},
53		}, "symbolList")
54
55	abitidy = pctx.AndroidStaticRule("abitidy",
56		blueprint.RuleParams{
57			Command:     "$abitidy --all $flags -i $in -o $out",
58			CommandDeps: []string{"$abitidy"},
59		}, "flags")
60
61	abidiff = pctx.AndroidStaticRule("abidiff",
62		blueprint.RuleParams{
63			// Need to create *some* output for ninja. We don't want to use tee
64			// because we don't want to spam the build output with "nothing
65			// changed" messages, so redirect output message to $out, and if
66			// changes were detected print the output and fail.
67			Command:     "$abidiff $args $in > $out || (cat $out && false)",
68			CommandDeps: []string{"$abidiff"},
69		}, "args")
70
71	ndkLibrarySuffix = ".ndk"
72
73	ndkKnownLibsKey = android.NewOnceKey("ndkKnownLibsKey")
74	// protects ndkKnownLibs writes during parallel BeginMutator.
75	ndkKnownLibsLock sync.Mutex
76
77	stubImplementation = dependencyTag{name: "stubImplementation"}
78)
79
80// The First_version and Unversioned_until properties of this struct should not
81// be used directly, but rather through the ApiLevel returning methods
82// firstVersion() and unversionedUntil().
83
84// Creates a stub shared library based on the provided version file.
85//
86// Example:
87//
88// ndk_library {
89//
90//	name: "libfoo",
91//	symbol_file: "libfoo.map.txt",
92//	first_version: "9",
93//
94// }
95type libraryProperties struct {
96	// Relative path to the symbol map.
97	// An example file can be seen here: TODO(danalbert): Make an example.
98	Symbol_file *string `android:"path"`
99
100	// The first API level a library was available. A library will be generated
101	// for every API level beginning with this one.
102	First_version *string
103
104	// The first API level that library should have the version script applied.
105	// This defaults to the value of first_version, and should almost never be
106	// used. This is only needed to work around platform bugs like
107	// https://github.com/android-ndk/ndk/issues/265.
108	Unversioned_until *string
109
110	// If true, does not emit errors when APIs lacking type information are
111	// found. This is false by default and should not be enabled outside bionic,
112	// where it is enabled pending a fix for http://b/190554910 (no debug info
113	// for asm implemented symbols).
114	Allow_untyped_symbols *bool
115
116	// Headers presented by this library to the Public API Surface
117	Export_header_libs []string
118}
119
120type stubDecorator struct {
121	*libraryDecorator
122
123	properties libraryProperties
124
125	versionScriptPath     android.ModuleGenPath
126	parsedCoverageXmlPath android.ModuleOutPath
127	installPath           android.Path
128	abiDumpPath           android.OutputPath
129	abiDiffPaths          android.Paths
130
131	apiLevel         android.ApiLevel
132	firstVersion     android.ApiLevel
133	unversionedUntil android.ApiLevel
134}
135
136var _ versionedInterface = (*stubDecorator)(nil)
137
138func shouldUseVersionScript(ctx BaseModuleContext, stub *stubDecorator) bool {
139	return stub.apiLevel.GreaterThanOrEqualTo(stub.unversionedUntil)
140}
141
142func (stub *stubDecorator) implementationModuleName(name string) string {
143	return strings.TrimSuffix(name, ndkLibrarySuffix)
144}
145
146func ndkLibraryVersions(ctx android.BaseMutatorContext, from android.ApiLevel) []string {
147	var versions []android.ApiLevel
148	versionStrs := []string{}
149	for _, version := range ctx.Config().AllSupportedApiLevels() {
150		if version.GreaterThanOrEqualTo(from) {
151			versions = append(versions, version)
152			versionStrs = append(versionStrs, version.String())
153		}
154	}
155	versionStrs = append(versionStrs, android.FutureApiLevel.String())
156
157	return versionStrs
158}
159
160func (this *stubDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
161	if !ctx.Module().Enabled() {
162		return nil
163	}
164	if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
165		ctx.Module().Disable()
166		return nil
167	}
168	firstVersion, err := nativeApiLevelFromUser(ctx,
169		String(this.properties.First_version))
170	if err != nil {
171		ctx.PropertyErrorf("first_version", err.Error())
172		return nil
173	}
174	return ndkLibraryVersions(ctx, firstVersion)
175}
176
177func (this *stubDecorator) initializeProperties(ctx BaseModuleContext) bool {
178	this.apiLevel = nativeApiLevelOrPanic(ctx, this.stubsVersion())
179
180	var err error
181	this.firstVersion, err = nativeApiLevelFromUser(ctx,
182		String(this.properties.First_version))
183	if err != nil {
184		ctx.PropertyErrorf("first_version", err.Error())
185		return false
186	}
187
188	str := proptools.StringDefault(this.properties.Unversioned_until, "minimum")
189	this.unversionedUntil, err = nativeApiLevelFromUser(ctx, str)
190	if err != nil {
191		ctx.PropertyErrorf("unversioned_until", err.Error())
192		return false
193	}
194
195	return true
196}
197
198func getNDKKnownLibs(config android.Config) *[]string {
199	return config.Once(ndkKnownLibsKey, func() interface{} {
200		return &[]string{}
201	}).(*[]string)
202}
203
204func (c *stubDecorator) compilerInit(ctx BaseModuleContext) {
205	c.baseCompiler.compilerInit(ctx)
206
207	name := ctx.baseModuleName()
208	if strings.HasSuffix(name, ndkLibrarySuffix) {
209		ctx.PropertyErrorf("name", "Do not append %q manually, just use the base name", ndkLibrarySuffix)
210	}
211
212	ndkKnownLibsLock.Lock()
213	defer ndkKnownLibsLock.Unlock()
214	ndkKnownLibs := getNDKKnownLibs(ctx.Config())
215	for _, lib := range *ndkKnownLibs {
216		if lib == name {
217			return
218		}
219	}
220	*ndkKnownLibs = append(*ndkKnownLibs, name)
221}
222
223var stubLibraryCompilerFlags = []string{
224	// We're knowingly doing some otherwise unsightly things with builtin
225	// functions here. We're just generating stub libraries, so ignore it.
226	"-Wno-incompatible-library-redeclaration",
227	"-Wno-incomplete-setjmp-declaration",
228	"-Wno-builtin-requires-header",
229	"-Wno-invalid-noreturn",
230	"-Wall",
231	"-Werror",
232	// These libraries aren't actually used. Don't worry about unwinding
233	// (avoids the need to link an unwinder into a fake library).
234	"-fno-unwind-tables",
235}
236
237func init() {
238	config.ExportStringList("StubLibraryCompilerFlags", stubLibraryCompilerFlags)
239}
240
241func addStubLibraryCompilerFlags(flags Flags) Flags {
242	flags.Global.CFlags = append(flags.Global.CFlags, stubLibraryCompilerFlags...)
243	// All symbols in the stubs library should be visible.
244	if inList("-fvisibility=hidden", flags.Local.CFlags) {
245		flags.Local.CFlags = append(flags.Local.CFlags, "-fvisibility=default")
246	}
247	return flags
248}
249
250func (stub *stubDecorator) compilerFlags(ctx ModuleContext, flags Flags, deps PathDeps) Flags {
251	flags = stub.baseCompiler.compilerFlags(ctx, flags, deps)
252	return addStubLibraryCompilerFlags(flags)
253}
254
255type ndkApiOutputs struct {
256	stubSrc       android.ModuleGenPath
257	versionScript android.ModuleGenPath
258	symbolList    android.ModuleGenPath
259}
260
261func parseNativeAbiDefinition(ctx ModuleContext, symbolFile string,
262	apiLevel android.ApiLevel, genstubFlags string) ndkApiOutputs {
263
264	stubSrcPath := android.PathForModuleGen(ctx, "stub.c")
265	versionScriptPath := android.PathForModuleGen(ctx, "stub.map")
266	symbolFilePath := android.PathForModuleSrc(ctx, symbolFile)
267	symbolListPath := android.PathForModuleGen(ctx, "abi_symbol_list.txt")
268	apiLevelsJson := android.GetApiLevelsJson(ctx)
269	ctx.Build(pctx, android.BuildParams{
270		Rule:        genStubSrc,
271		Description: "generate stubs " + symbolFilePath.Rel(),
272		Outputs: []android.WritablePath{stubSrcPath, versionScriptPath,
273			symbolListPath},
274		Input:     symbolFilePath,
275		Implicits: []android.Path{apiLevelsJson},
276		Args: map[string]string{
277			"arch":     ctx.Arch().ArchType.String(),
278			"apiLevel": apiLevel.String(),
279			"apiMap":   apiLevelsJson.String(),
280			"flags":    genstubFlags,
281		},
282	})
283
284	return ndkApiOutputs{
285		stubSrc:       stubSrcPath,
286		versionScript: versionScriptPath,
287		symbolList:    symbolListPath,
288	}
289}
290
291func compileStubLibrary(ctx ModuleContext, flags Flags, src android.Path) Objects {
292	// libc/libm stubs libraries end up mismatching with clang's internal definition of these
293	// functions (which have noreturn attributes and other things). Because we just want to create a
294	// stub with symbol definitions, and types aren't important in C, ignore the mismatch.
295	flags.Local.ConlyFlags = append(flags.Local.ConlyFlags, "-fno-builtin")
296	return compileObjs(ctx, flagsToBuilderFlags(flags), "",
297		android.Paths{src}, nil, nil, nil, nil)
298}
299
300func (this *stubDecorator) findImplementationLibrary(ctx ModuleContext) android.Path {
301	dep := ctx.GetDirectDepWithTag(strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix),
302		stubImplementation)
303	if dep == nil {
304		ctx.ModuleErrorf("Could not find implementation for stub")
305		return nil
306	}
307	impl, ok := dep.(*Module)
308	if !ok {
309		ctx.ModuleErrorf("Implementation for stub is not correct module type")
310		return nil
311	}
312	output := impl.UnstrippedOutputFile()
313	if output == nil {
314		ctx.ModuleErrorf("implementation module (%s) has no output", impl)
315		return nil
316	}
317
318	return output
319}
320
321func (this *stubDecorator) libraryName(ctx ModuleContext) string {
322	return strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix)
323}
324
325func (this *stubDecorator) findPrebuiltAbiDump(ctx ModuleContext,
326	apiLevel android.ApiLevel) android.OptionalPath {
327
328	subpath := filepath.Join("prebuilts/abi-dumps/ndk", apiLevel.String(),
329		ctx.Arch().ArchType.String(), this.libraryName(ctx), "abi.xml")
330	return android.ExistentPathForSource(ctx, subpath)
331}
332
333// Feature flag.
334func canDumpAbi(config android.Config) bool {
335	if runtime.GOOS == "darwin" {
336		return false
337	}
338	// abidw doesn't currently handle top-byte-ignore correctly. Disable ABI
339	// dumping for those configs while we wait for a fix. We'll still have ABI
340	// checking coverage from non-hwasan builds.
341	// http://b/190554910
342	if android.InList("hwaddress", config.SanitizeDevice()) {
343		return false
344	}
345	// http://b/156513478
346	// http://b/277624006
347	// This step is expensive. We're not able to do anything with the outputs of
348	// this step yet (canDiffAbi is flagged off because libabigail isn't able to
349	// handle all our libraries), disable it. There's no sense in protecting
350	// against checking in code that breaks abidw since by the time any of this
351	// can be turned on we'll need to migrate to STG anyway.
352	return false
353}
354
355// Feature flag to disable diffing against prebuilts.
356func canDiffAbi() bool {
357	return false
358}
359
360func (this *stubDecorator) dumpAbi(ctx ModuleContext, symbolList android.Path) {
361	implementationLibrary := this.findImplementationLibrary(ctx)
362	abiRawPath := getNdkAbiDumpInstallBase(ctx).Join(ctx,
363		this.apiLevel.String(), ctx.Arch().ArchType.String(),
364		this.libraryName(ctx), "abi.raw.xml")
365	ctx.Build(pctx, android.BuildParams{
366		Rule:        abidw,
367		Description: fmt.Sprintf("abidw %s", implementationLibrary),
368		Input:       implementationLibrary,
369		Output:      abiRawPath,
370		Implicit:    symbolList,
371		Args: map[string]string{
372			"symbolList": symbolList.String(),
373		},
374	})
375
376	this.abiDumpPath = getNdkAbiDumpInstallBase(ctx).Join(ctx,
377		this.apiLevel.String(), ctx.Arch().ArchType.String(),
378		this.libraryName(ctx), "abi.xml")
379	untypedFlag := "--abort-on-untyped-symbols"
380	if proptools.BoolDefault(this.properties.Allow_untyped_symbols, false) {
381		untypedFlag = ""
382	}
383	ctx.Build(pctx, android.BuildParams{
384		Rule:        abitidy,
385		Description: fmt.Sprintf("abitidy %s", implementationLibrary),
386		Input:       abiRawPath,
387		Output:      this.abiDumpPath,
388		Args: map[string]string{
389			"flags": untypedFlag,
390		},
391	})
392}
393
394func findNextApiLevel(ctx ModuleContext, apiLevel android.ApiLevel) *android.ApiLevel {
395	apiLevels := append(ctx.Config().AllSupportedApiLevels(),
396		android.FutureApiLevel)
397	for _, api := range apiLevels {
398		if api.GreaterThan(apiLevel) {
399			return &api
400		}
401	}
402	return nil
403}
404
405func (this *stubDecorator) diffAbi(ctx ModuleContext) {
406	// Catch any ABI changes compared to the checked-in definition of this API
407	// level.
408	abiDiffPath := android.PathForModuleOut(ctx, "abidiff.timestamp")
409	prebuiltAbiDump := this.findPrebuiltAbiDump(ctx, this.apiLevel)
410	missingPrebuiltError := fmt.Sprintf(
411		"Did not find prebuilt ABI dump for %q (%q). Generate with "+
412			"//development/tools/ndk/update_ndk_abi.sh.", this.libraryName(ctx),
413		prebuiltAbiDump.InvalidReason())
414	if !prebuiltAbiDump.Valid() {
415		ctx.Build(pctx, android.BuildParams{
416			Rule:   android.ErrorRule,
417			Output: abiDiffPath,
418			Args: map[string]string{
419				"error": missingPrebuiltError,
420			},
421		})
422	} else {
423		ctx.Build(pctx, android.BuildParams{
424			Rule: abidiff,
425			Description: fmt.Sprintf("abidiff %s %s", prebuiltAbiDump,
426				this.abiDumpPath),
427			Output: abiDiffPath,
428			Inputs: android.Paths{prebuiltAbiDump.Path(), this.abiDumpPath},
429		})
430	}
431	this.abiDiffPaths = append(this.abiDiffPaths, abiDiffPath)
432
433	// Also ensure that the ABI of the next API level (if there is one) matches
434	// this API level. *New* ABI is allowed, but any changes to APIs that exist
435	// in this API level are disallowed.
436	if !this.apiLevel.IsCurrent() {
437		nextApiLevel := findNextApiLevel(ctx, this.apiLevel)
438		if nextApiLevel == nil {
439			panic(fmt.Errorf("could not determine which API level follows "+
440				"non-current API level %s", this.apiLevel))
441		}
442		nextAbiDiffPath := android.PathForModuleOut(ctx,
443			"abidiff_next.timestamp")
444		nextAbiDump := this.findPrebuiltAbiDump(ctx, *nextApiLevel)
445		if !nextAbiDump.Valid() {
446			ctx.Build(pctx, android.BuildParams{
447				Rule:   android.ErrorRule,
448				Output: nextAbiDiffPath,
449				Args: map[string]string{
450					"error": missingPrebuiltError,
451				},
452			})
453		} else {
454			ctx.Build(pctx, android.BuildParams{
455				Rule: abidiff,
456				Description: fmt.Sprintf("abidiff %s %s", this.abiDumpPath,
457					nextAbiDump),
458				Output: nextAbiDiffPath,
459				Inputs: android.Paths{this.abiDumpPath, nextAbiDump.Path()},
460				Args: map[string]string{
461					"args": "--no-added-syms",
462				},
463			})
464		}
465		this.abiDiffPaths = append(this.abiDiffPaths, nextAbiDiffPath)
466	}
467}
468
469func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
470	if !strings.HasSuffix(String(c.properties.Symbol_file), ".map.txt") {
471		ctx.PropertyErrorf("symbol_file", "must end with .map.txt")
472	}
473
474	if !c.buildStubs() {
475		// NDK libraries have no implementation variant, nothing to do
476		return Objects{}
477	}
478
479	if !c.initializeProperties(ctx) {
480		// Emits its own errors, so we don't need to.
481		return Objects{}
482	}
483
484	symbolFile := String(c.properties.Symbol_file)
485	nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile, c.apiLevel, "")
486	objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
487	c.versionScriptPath = nativeAbiResult.versionScript
488	if canDumpAbi(ctx.Config()) {
489		c.dumpAbi(ctx, nativeAbiResult.symbolList)
490		if canDiffAbi() {
491			c.diffAbi(ctx)
492		}
493	}
494	if c.apiLevel.IsCurrent() && ctx.PrimaryArch() {
495		c.parsedCoverageXmlPath = parseSymbolFileForAPICoverage(ctx, symbolFile)
496	}
497	return objs
498}
499
500// Add a dependency on the header modules of this ndk_library
501func (linker *stubDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps {
502	return Deps{
503		HeaderLibs: linker.properties.Export_header_libs,
504	}
505}
506
507func (linker *stubDecorator) Name(name string) string {
508	return name + ndkLibrarySuffix
509}
510
511func (stub *stubDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
512	stub.libraryDecorator.libName = ctx.baseModuleName()
513	return stub.libraryDecorator.linkerFlags(ctx, flags)
514}
515
516func (stub *stubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps,
517	objs Objects) android.Path {
518
519	if !stub.buildStubs() {
520		// NDK libraries have no implementation variant, nothing to do
521		return nil
522	}
523
524	if shouldUseVersionScript(ctx, stub) {
525		linkerScriptFlag := "-Wl,--version-script," + stub.versionScriptPath.String()
526		flags.Local.LdFlags = append(flags.Local.LdFlags, linkerScriptFlag)
527		flags.LdFlagsDeps = append(flags.LdFlagsDeps, stub.versionScriptPath)
528	}
529
530	stub.libraryDecorator.skipAPIDefine = true
531	return stub.libraryDecorator.link(ctx, flags, deps, objs)
532}
533
534func (stub *stubDecorator) nativeCoverage() bool {
535	return false
536}
537
538// Returns the install path for unversioned NDK libraries (currently only static
539// libraries).
540func getUnversionedLibraryInstallPath(ctx ModuleContext) android.InstallPath {
541	return getNdkSysrootBase(ctx).Join(ctx, "usr/lib", config.NDKTriple(ctx.toolchain()))
542}
543
544// Returns the install path for versioned NDK libraries. These are most often
545// stubs, but the same paths are used for CRT objects.
546func getVersionedLibraryInstallPath(ctx ModuleContext, apiLevel android.ApiLevel) android.InstallPath {
547	return getUnversionedLibraryInstallPath(ctx).Join(ctx, apiLevel.String())
548}
549
550func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) {
551	installDir := getVersionedLibraryInstallPath(ctx, stub.apiLevel)
552	stub.installPath = ctx.InstallFile(installDir, path.Base(), path)
553}
554
555func newStubLibrary() *Module {
556	module, library := NewLibrary(android.DeviceSupported)
557	library.BuildOnlyShared()
558	module.stl = nil
559	module.sanitize = nil
560	library.disableStripping()
561
562	stub := &stubDecorator{
563		libraryDecorator: library,
564	}
565	module.compiler = stub
566	module.linker = stub
567	module.installer = stub
568	module.library = stub
569
570	module.Properties.AlwaysSdk = true
571	module.Properties.Sdk_version = StringPtr("current")
572
573	module.AddProperties(&stub.properties, &library.MutatedProperties)
574
575	return module
576}
577
578// ndk_library creates a library that exposes a stub implementation of functions
579// and variables for use at build time only.
580func NdkLibraryFactory() android.Module {
581	module := newStubLibrary()
582	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibBoth)
583	android.InitBazelModule(module)
584	return module
585}
586
587type bazelCcApiContributionAttributes struct {
588	Api          bazel.LabelAttribute
589	Api_surfaces bazel.StringListAttribute
590	Hdrs         bazel.LabelListAttribute
591	Library_name string
592}
593
594// Names of the cc_api_header targets in the bp2build workspace
595func apiHeaderLabels(ctx android.TopDownMutatorContext, hdrLibs []string) bazel.LabelList {
596	addSuffix := func(ctx android.BazelConversionPathContext, module blueprint.Module) string {
597		label := android.BazelModuleLabel(ctx, module)
598		return android.ApiContributionTargetName(label)
599	}
600	return android.BazelLabelForModuleDepsWithFn(ctx, hdrLibs, addSuffix)
601}
602
603func ndkLibraryBp2build(ctx android.TopDownMutatorContext, m *Module) {
604	props := bazel.BazelTargetModuleProperties{
605		Rule_class:        "cc_api_contribution",
606		Bzl_load_location: "//build/bazel/rules/apis:cc_api_contribution.bzl",
607	}
608	stubLibrary := m.compiler.(*stubDecorator)
609	attrs := &bazelCcApiContributionAttributes{
610		Library_name: stubLibrary.implementationModuleName(m.Name()),
611		Api_surfaces: bazel.MakeStringListAttribute(
612			[]string{android.PublicApi.String()}),
613	}
614	if symbolFile := stubLibrary.properties.Symbol_file; symbolFile != nil {
615		apiLabel := android.BazelLabelForModuleSrcSingle(ctx, proptools.String(symbolFile)).Label
616		attrs.Api = *bazel.MakeLabelAttribute(apiLabel)
617	}
618	apiHeaders := apiHeaderLabels(ctx, stubLibrary.properties.Export_header_libs)
619	attrs.Hdrs = bazel.MakeLabelListAttribute(apiHeaders)
620	apiContributionTargetName := android.ApiContributionTargetName(ctx.ModuleName())
621	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: apiContributionTargetName}, attrs)
622}
623