diff options
author | rillig <rillig@pkgsrc.org> | 2020-03-26 07:02:44 +0000 |
---|---|---|
committer | rillig <rillig@pkgsrc.org> | 2020-03-26 07:02:44 +0000 |
commit | 46b25f8b73a52498da1ed2db1c738110c77f6ea4 (patch) | |
tree | 0ccbcd04809e11718177a80a34acd06591ba7e96 | |
parent | 1c6e71ee7f2f054721785d640cacaff36bf74e76 (diff) | |
download | pkgsrc-46b25f8b73a52498da1ed2db1c738110c77f6ea4.tar.gz |
pkgtools/pkglint: update to 20.1.1
Changes since 20.1.0:
In UNLIMIT_RESOURCES, the recently added virtualsize is allowed.
Packages that have distfiles without any digit in their name should
define DIST_SUBDIR to avoid polluting the global namespace. The
top-level distfiles directory should only contain versioned filenames.
-rw-r--r-- | pkgtools/pkglint/Makefile | 4 | ||||
-rw-r--r-- | pkgtools/pkglint/files/distinfo.go | 29 | ||||
-rw-r--r-- | pkgtools/pkglint/files/distinfo_test.go | 42 | ||||
-rw-r--r-- | pkgtools/pkglint/files/mkline.go | 6 | ||||
-rw-r--r-- | pkgtools/pkglint/files/mklines.go | 12 | ||||
-rw-r--r-- | pkgtools/pkglint/files/package.go | 47 | ||||
-rw-r--r-- | pkgtools/pkglint/files/package_test.go | 118 | ||||
-rw-r--r-- | pkgtools/pkglint/files/pkglint.go | 2 | ||||
-rw-r--r-- | pkgtools/pkglint/files/shell.go | 2 | ||||
-rw-r--r-- | pkgtools/pkglint/files/shell_test.go | 15 | ||||
-rw-r--r-- | pkgtools/pkglint/files/util.go | 15 | ||||
-rw-r--r-- | pkgtools/pkglint/files/vardefs.go | 5 |
12 files changed, 289 insertions, 8 deletions
diff --git a/pkgtools/pkglint/Makefile b/pkgtools/pkglint/Makefile index 742ce1041e0..fabebbc5986 100644 --- a/pkgtools/pkglint/Makefile +++ b/pkgtools/pkglint/Makefile @@ -1,6 +1,6 @@ -# $NetBSD: Makefile,v 1.638 2020/03/23 19:55:08 rillig Exp $ +# $NetBSD: Makefile,v 1.639 2020/03/26 07:02:44 rillig Exp $ -PKGNAME= pkglint-20.1.0 +PKGNAME= pkglint-20.1.1 CATEGORIES= pkgtools DISTNAME= tools MASTER_SITES= ${MASTER_SITE_GITHUB:=golang/} diff --git a/pkgtools/pkglint/files/distinfo.go b/pkgtools/pkglint/files/distinfo.go index 0dcb7353384..ef82da1446a 100644 --- a/pkgtools/pkglint/files/distinfo.go +++ b/pkgtools/pkglint/files/distinfo.go @@ -34,6 +34,13 @@ func CheckLinesDistinfo(pkg *Package, lines *Lines) { CheckLinesTrailingEmptyLines(lines) ck.checkUnrecordedPatches() + if pkg != nil { + pkg.distinfoDistfiles = make(map[string]bool) + for path := range ck.infos { + pkg.distinfoDistfiles[path.Base()] = true + } + } + SaveAutofixChanges(lines) } @@ -106,6 +113,7 @@ func (ck *distinfoLinesChecker) check() { for _, filename := range ck.filenames { info := ck.infos[filename] + ck.checkFilename(filename, info) ck.checkAlgorithms(info) for _, hash := range info.hashes { ck.checkGlobalDistfileMismatch(hash) @@ -116,6 +124,18 @@ func (ck *distinfoLinesChecker) check() { } } +func (ck *distinfoLinesChecker) checkFilename(filename RelPath, info distinfoFileInfo) { + if info.isPatch != no || !info.hasDistfileAlgorithms() || matches(filename.String(), `\d`) { + return + } + + line := info.line() + line.Warnf( + "Distfiles without version number should be placed in a versioned DIST_SUBDIR.") + line.Explain( + seeGuide("How to handle modified distfiles with the 'old' name", "modified-distfiles-same-name")) +} + func (ck *distinfoLinesChecker) checkAlgorithms(info distinfoFileInfo) { filename := info.filename() algorithms := info.algorithms() @@ -419,6 +439,15 @@ func (info *distinfoFileInfo) algorithms() string { return strings.Join(algs, ", ") } +func (info *distinfoFileInfo) hasDistfileAlgorithms() bool { + h := info.hashes + return len(h) == 4 && + h[0].algorithm == "SHA1" && + h[1].algorithm == "RMD160" && + h[2].algorithm == "SHA512" && + h[3].algorithm == "Size" +} + type distinfoHash struct { line *Line filename RelPath diff --git a/pkgtools/pkglint/files/distinfo_test.go b/pkgtools/pkglint/files/distinfo_test.go index f590e4bd120..c4825bd5f26 100644 --- a/pkgtools/pkglint/files/distinfo_test.go +++ b/pkgtools/pkglint/files/distinfo_test.go @@ -14,8 +14,8 @@ func (s *Suite) Test_CheckLinesDistinfo__parse_errors(c *check.C) { lines := t.SetUpFileLines("distinfo", "should be the CVS ID", "should be empty", - "MD5 (distfile.tar.gz) = 12345678901234567890123456789012", - "SHA1 (distfile.tar.gz) = 1234567890123456789012345678901234567890", + "MD5 (distfile-1.0.tar.gz) = 12345678901234567890123456789012", + "SHA1 (distfile-1.0.tar.gz) = 1234567890123456789012345678901234567890", "SHA1 (patch-aa) = 6b98dd609f85a9eb9c4c1e4e7055a6aaa62b7cc7", "Size (patch-aa) = 104", "SHA1 (patch-ab) = 6b98dd609f85a9eb9c4c1e4e7055a6aaa62b7cc7", @@ -31,7 +31,7 @@ func (s *Suite) Test_CheckLinesDistinfo__parse_errors(c *check.C) { "ERROR: distinfo:1: Invalid line: should be the CVS ID", "ERROR: distinfo:2: Invalid line: should be empty", "ERROR: distinfo:8: Invalid line: Another invalid line", - "ERROR: distinfo:3: Expected SHA1, RMD160, SHA512, Size checksums for \"distfile.tar.gz\", got MD5, SHA1.", + "ERROR: distinfo:3: Expected SHA1, RMD160, SHA512, Size checksums for \"distfile-1.0.tar.gz\", got MD5, SHA1.", "ERROR: distinfo:5: Expected SHA1 hash for patch-aa, got SHA1, Size.", "WARN: distinfo:9: Patch file \"patch-nonexistent\" does not exist in directory \"patches\".") } @@ -204,6 +204,41 @@ func (s *Suite) Test_distinfoLinesChecker_check__missing_php_patches(c *check.C) t.CheckOutputEmpty() } +func (s *Suite) Test_distinfoLinesChecker_checkFilename(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("category/package") + t.CreateFileLines("category/package/distinfo", + CvsID, + "", + "SHA1 (ok-1.0.tar.gz) = 1234", + "RMD160 (ok-1.0.tar.gz) = 1234", + "SHA512 (ok-1.0.tar.gz) = 1234", + "Size (ok-1.0.tar.gz) = 1234", + "SHA1 (not-ok.tar.gz) = 1234", + "RMD160 (not-ok.tar.gz) = 1234", + "SHA512 (not-ok.tar.gz) = 1234", + "Size (not-ok.tar.gz) = 1234", + "SHA1 (non-versioned/not-ok.tar.gz) = 1234", + "RMD160 (non-versioned/not-ok.tar.gz) = 1234", + "SHA512 (non-versioned/not-ok.tar.gz) = 1234", + "Size (non-versioned/not-ok.tar.gz) = 1234", + "SHA1 (versioned-1/ok.tar.gz) = 1234", + "RMD160 (versioned-1/ok.tar.gz) = 1234", + "SHA512 (versioned-1/ok.tar.gz) = 1234", + "Size (versioned-1/ok.tar.gz) = 1234") + t.Chdir("category/package") + t.FinishSetUp() + + G.Check(".") + + t.CheckOutputLines( + "WARN: distinfo:7: Distfiles without version number "+ + "should be placed in a versioned DIST_SUBDIR.", + "WARN: distinfo:11: Distfiles without version number "+ + "should be placed in a versioned DIST_SUBDIR.") +} + func (s *Suite) Test_distinfoLinesChecker_checkAlgorithms__nonexistent_distfile_called_patch(c *check.C) { t := s.Init(c) @@ -653,6 +688,7 @@ func (s *Suite) Test_distinfoLinesChecker_checkUnrecordedPatches(c *check.C) { G.checkdirPackage(".") t.CheckOutputLines( + "WARN: distinfo:3: Distfiles without version number should be placed in a versioned DIST_SUBDIR.", "ERROR: distinfo: Patch \"patches/patch-aa\" is not recorded. Run \""+confMake+" makepatchsum\".", "ERROR: distinfo: Patch \"patches/patch-src-Makefile\" is not recorded. Run \""+confMake+" makepatchsum\".") } diff --git a/pkgtools/pkglint/files/mkline.go b/pkgtools/pkglint/files/mkline.go index f4c881d76fe..4588fa49270 100644 --- a/pkgtools/pkglint/files/mkline.go +++ b/pkgtools/pkglint/files/mkline.go @@ -293,12 +293,18 @@ func (mkline *MkLine) Args() string { return mkline.data.(*mkLineDirective).args func (mkline *MkLine) Cond() *MkCond { cond := mkline.data.(*mkLineDirective).cond if cond == nil { + assert(mkline.HasCond()) cond = NewMkParser(mkline.Line, mkline.Args()).MkCond() mkline.data.(*mkLineDirective).cond = cond } return cond } +func (mkline *MkLine) HasCond() bool { + directive := mkline.Directive() + return directive == "if" || directive == "elif" +} + // DirectiveComment is the trailing end-of-line comment, typically at a deeply nested .endif or .endfor. func (mkline *MkLine) DirectiveComment() string { return mkline.data.(*mkLineDirective).comment } diff --git a/pkgtools/pkglint/files/mklines.go b/pkgtools/pkglint/files/mklines.go index 3a77ac31733..3140067940c 100644 --- a/pkgtools/pkglint/files/mklines.go +++ b/pkgtools/pkglint/files/mklines.go @@ -653,6 +653,18 @@ func (mklines *MkLines) ExpandLoopVar(varname string) []string { return nil } +// IsUnreachable determines whether the given line is unreachable because a +// condition on the way to that line is not satisfied. +// If unsure, returns false. +func (mklines *MkLines) IsUnreachable(mkline *MkLine) bool { + // To make this code as simple as possible, the code should operate + // on a high-level AST, where the nodes are If, For and BasicBlock. + // + // See lang/ghc*/bootstrap.mk for good examples how pkglint should + // treat variable assignments. It's getting complicated. + return false +} + func (mklines *MkLines) SaveAutofixChanges() { mklines.lines.SaveAutofixChanges() } diff --git a/pkgtools/pkglint/files/package.go b/pkgtools/pkglint/files/package.go index d1625fea55c..83c73af5438 100644 --- a/pkgtools/pkglint/files/package.go +++ b/pkgtools/pkglint/files/package.go @@ -86,6 +86,10 @@ type Package struct { IgnoreMissingPatches bool // In distinfo, don't warn about patches that cannot be found. Once Once + + // Contains the basenames of the distfiles that are mentioned in distinfo, + // for example "package-1.0.tar.gz", even if that file is in a DIST_SUBDIR. + distinfoDistfiles map[string]bool } func NewPackage(dir CurrPath) *Package { @@ -121,6 +125,7 @@ func NewPackage(dir CurrPath) *Package { pkg.vars.Fallback("PATCHDIR", "patches") pkg.vars.Fallback("KRB5_TYPE", "heimdal") pkg.vars.Fallback("PGSQL_VERSION", "95") + pkg.vars.Fallback("EXTRACT_SUFX", ".tar.gz") // In reality, this is an absolute pathname. Since this variable is // typically used in the form ${.CURDIR}/../../somewhere, this doesn't @@ -132,6 +137,9 @@ func NewPackage(dir CurrPath) *Package { func (pkg *Package) Check() { files, mklines, allLines := pkg.load() + if files == nil { + return + } pkg.check(files, mklines, allLines) } @@ -588,6 +596,8 @@ func (pkg *Package) check(filenames []CurrPath, mklines, allLines *MkLines) { pkg.checkDescr(filenames, mklines) } + + pkg.checkDistfilesInDistinfo(allLines) } func (pkg *Package) checkDescr(filenames []CurrPath, mklines *MkLines) { @@ -605,6 +615,43 @@ func (pkg *Package) checkDescr(filenames []CurrPath, mklines *MkLines) { mklines.Whole().Errorf("Each package must have a DESCR file.") } +func (pkg *Package) checkDistfilesInDistinfo(mklines *MkLines) { + // Needs more work; see MkLines.IsUnreachable. + if !G.Experimental { + return + } + + if pkg.distinfoDistfiles == nil { + return + } + + redundant := pkg.redundant + distfiles := redundant.get("DISTFILES") + if distfiles == nil { + return + } + + for _, mkline := range distfiles.vari.WriteLocations() { + unreachable := newLazyBool( + func() bool { return mklines.IsUnreachable(mkline) }) + resolved := resolveVariableRefs(mkline.Value(), nil, pkg) + + for _, distfile := range mkline.ValueFields(resolved) { + if containsVarUse(distfile) { + continue + } + if pkg.distinfoDistfiles[NewPath(distfile).Base()] { + continue + } + if unreachable.get() { + continue + } + mkline.Warnf("Distfile %q is not mentioned in %s.", + distfile, mkline.Rel(pkg.File(pkg.DistinfoFile))) + } + } +} + func (pkg *Package) checkfilePackageMakefile(filename CurrPath, mklines *MkLines, allLines *MkLines) { if trace.Tracing { defer trace.Call(filename)() diff --git a/pkgtools/pkglint/files/package_test.go b/pkgtools/pkglint/files/package_test.go index 1cb6e2ab983..03a8bb3857e 100644 --- a/pkgtools/pkglint/files/package_test.go +++ b/pkgtools/pkglint/files/package_test.go @@ -1344,6 +1344,124 @@ func (s *Suite) Test_Package_checkDescr__DESCR_SRC(c *check.C) { t.CheckOutputEmpty() } +// All files that can possibly be added to DISTFILES need a corresponding +// entry in the distinfo file. +// +// https://mail-index.netbsd.org/pkgsrc-changes/2020/02/05/msg206172.html +// https://mail-index.netbsd.org/pkgsrc-changes/2020/03/25/msg209445.html +func (s *Suite) Test_Package_checkDistfilesInDistinfo__indirect_conditional_DISTFILES(c *check.C) { + G.Experimental = true + + t := s.Init(c) + + t.SetUpPackage("category/package", + ".include \"../../mk/bsd.prefs.mk\"", + "", + "DISTFILES.i386=\t\tdistfile-i386.tar.gz", + "DISTFILES.other=\tdistfile-other.tar.gz", + "", + ".if ${MACHINE_ARCH} == i386", + "DISTFILES+=\t${DISTFILES.i386}", + ".else", + "DISTFILES+=\t${DISTFILES.other}", + ".endif", + "", + "DISTFILES+=\tok-3.tar.gz") + t.CreateFileLines("category/package/distinfo", + CvsID, + "", + "SHA1 (ok-3.tar.gz) = 1234", + "RMD160 (ok-3.tar.gz) = 1234", + "SHA512 (ok-3.tar.gz) = 1234", + "Size (ok-3.tar.gz) = 1234", + "SHA1 (package-1.0.tar.gz) = 1234", + "RMD160 (package-1.0.tar.gz) = 1234", + "SHA512 (package-1.0.tar.gz) = 1234", + "Size (package-1.0.tar.gz) = 1234") + t.Chdir("category/package") + t.FinishSetUp() + + G.Check(".") + + t.CheckOutputLines( + "WARN: Makefile:26: Distfile \"distfile-i386.tar.gz\" is not mentioned in distinfo.", + "WARN: Makefile:28: Distfile \"distfile-other.tar.gz\" is not mentioned in distinfo.") +} + +func (s *Suite) Test_Package_checkDistfilesInDistinfo__indirect_DIST_SUBDIR(c *check.C) { + G.Experimental = true + + t := s.Init(c) + + t.SetUpPackage("category/package", + ".include \"../../mk/bsd.prefs.mk\"", + "", + // As of 2020-03-26, pkglint doesn't know how to resolve PKGNAME_NOREV. + "DIST_SUBDIR=\t${PKGNAME_NOREV}", + // Strictly speaking, this is redundant, but as of 2020-03-26, + // pkglint doesn't infer the default DISTFILES, so it needs a bit of help here. + "DISTFILES+=\tdistfile-1.0.tar.gz", + "DISTFILES+=\tdistfile-other.tar.gz") + t.CreateFileLines("distinfo", + CvsID, + "", + "SHA1 (package-1.0/distfile-other.tar.gz) = 1234", + "RMD160 (package-1.0/distfile-other.tar.gz) = 1234", + "SHA512 (package-1.0/distfile-other.tar.gz) = 1234", + "Size (package-1.0/distfile-other.tar.gz) = 1234", + "SHA1 (package-1.0/package-1.0.tar.gz) = 1234", + "RMD160 (package-1.0/package-1.0.tar.gz) = 1234", + "SHA512 (package-1.0/package-1.0.tar.gz) = 1234", + "Size (package-1.0/package-1.0.tar.gz) = 1234") + t.Chdir("category/package") + t.FinishSetUp() + + G.Check(".") + + t.CheckOutputLines( + "WARN: Makefile:24: Distfile \"distfile-other.tar.gz\" is not mentioned in distinfo.") +} + +func (s *Suite) Test_Package_checkDistfilesInDistinfo__depending_on_package_settable(c *check.C) { + G.Experimental = true + + t := s.Init(c) + + t.SetUpPackage("print/tex-varisize", + "DISTNAME=\tvarisize", + "PKGNAME=\ttex-${DISTNAME}-2014", + "TEXLIVE_REV=\t15878", + "", + "TEXLIVE_UNVERSIONED=\tyes", + "", + ".include \"../../print/texlive/package.mk\"") + t.CreateFileLines("print/tex-varisize/distinfo", + CvsID, + "", + "SHA1 (tex-varisize-15878/varisize.tar.xz) = 1234", + "RMD160 (tex-varisize-15878/varisize.tar.xz) = 1234", + "SHA512 (tex-varisize-15878/varisize.tar.xz) = 1234", + "Size (tex-varisize-15878/varisize.tar.xz) = 3176 bytes") + t.CreateFileLines("print/texlive/package.mk", + MkCvsID, + "", + ".if empty(TEXLIVE_UNVERSIONED)", + "DISTFILES?=\t${DISTNAME}.r${TEXLIVE_REV}${EXTRACT_SUFX}", + ".endif") + t.Chdir("print/tex-varisize") + t.FinishSetUp() + + G.Check(".") + + // The package-settable TEXLIVE_UNVERSIONED is definitely not empty, + // therefore the line in package.mk doesn't apply. + // FIXME: This warning is wrong because the line in package.mk is unreachable. + // See MkLines.IsUnreachable. + t.CheckOutputLines( + "WARN: ../../print/texlive/package.mk:4: Distfile \"varisize.r15878.tar.gz\" " + + "is not mentioned in ../../print/tex-varisize/distinfo.") +} + func (s *Suite) Test_Package_checkfilePackageMakefile__GNU_CONFIGURE(c *check.C) { t := s.Init(c) diff --git a/pkgtools/pkglint/files/pkglint.go b/pkgtools/pkglint/files/pkglint.go index 89dd4479bd4..7732369a113 100644 --- a/pkgtools/pkglint/files/pkglint.go +++ b/pkgtools/pkglint/files/pkglint.go @@ -401,7 +401,7 @@ func resolveVariableRefs(text string, mklines *MkLines, pkg *Package) string { str := text for { // TODO: Replace regular expression with full parser. - replaced := replaceAllFunc(str, `\$\{([\w.]+)\}`, replace) + replaced := replaceAllFunc(str, `\$\{([\w.\-]+)\}`, replace) if replaced == str { if trace.Tracing && str != text { trace.Stepf("resolveVariableRefs %q => %q", text, replaced) diff --git a/pkgtools/pkglint/files/shell.go b/pkgtools/pkglint/files/shell.go index 0fbe14c344f..e174ecd88ff 100644 --- a/pkgtools/pkglint/files/shell.go +++ b/pkgtools/pkglint/files/shell.go @@ -657,6 +657,8 @@ func (ck *ShellLineChecker) CheckShellCommandLine(shelltext string) { line.Errorf("Use of _PKG_SILENT and _PKG_DEBUG is obsolete. Use ${RUN} instead.") } } + lexer.SkipHspace() + lexer.SkipString("${_ULIMIT_CMD}") // It brings its own semicolon, just like ${RUN}. ck.CheckShellCommand(lexer.Rest(), &setE, RunTime) ck.checkMultiLineComment() diff --git a/pkgtools/pkglint/files/shell_test.go b/pkgtools/pkglint/files/shell_test.go index fc79c381d09..99700fdb663 100644 --- a/pkgtools/pkglint/files/shell_test.go +++ b/pkgtools/pkglint/files/shell_test.go @@ -587,6 +587,21 @@ func (s *Suite) Test_SimpleCommandChecker_checkEchoN(c *check.C) { "WARN: Makefile:4: Please use ${ECHO_N} instead of \"echo -n\".") } +// Before 2020-03-25, pkglint ran into a parse error since it didn't +// know that _ULIMIT_CMD brings its own semicolon. +func (s *Suite) Test_ShellLineChecker__skip_ULIMIT_CMD(c *check.C) { + t := s.Init(c) + + mklines := t.NewMkLines("Makefile", + MkCvsID, + "pre-configure:", + "\t${RUN} ${_ULIMIT_CMD} while :; do :; done") + + mklines.Check() + + t.CheckOutputEmpty() +} + func (s *Suite) Test_ShellLineChecker_checkConditionalCd(c *check.C) { t := s.Init(c) diff --git a/pkgtools/pkglint/files/util.go b/pkgtools/pkglint/files/util.go index 14a45565145..a10f8328c8e 100644 --- a/pkgtools/pkglint/files/util.go +++ b/pkgtools/pkglint/files/util.go @@ -1540,3 +1540,18 @@ type bagEntry struct { key interface{} count int } + +type lazyBool struct { + fn func() bool + value bool +} + +func newLazyBool(fn func() bool) *lazyBool { return &lazyBool{fn, false} } + +func (b *lazyBool) get() bool { + if b.fn != nil { + b.value = b.fn() + b.fn = nil + } + return b.value +} diff --git a/pkgtools/pkglint/files/vardefs.go b/pkgtools/pkglint/files/vardefs.go index 1e9781d6618..460739748ab 100644 --- a/pkgtools/pkglint/files/vardefs.go +++ b/pkgtools/pkglint/files/vardefs.go @@ -287,7 +287,7 @@ func (reg *VarTypeRegistry) compilerLanguages(src *Pkgsrc) *BasicType { } } - if mkline.IsDirective() && mkline.Cond() != nil { + if mkline.IsDirective() && mkline.HasCond() && mkline.Cond() != nil { mkline.Cond().Walk(&MkCondCallback{ VarUse: func(varuse *MkVarUse) { if varuse.varname == "USE_LANGUAGES" && len(varuse.modifiers) == 1 { @@ -1691,7 +1691,7 @@ func (reg *VarTypeRegistry) Init(src *Pkgsrc) { reg.syslist("TOUCH_FLAGS", BtShellWord) reg.pkglist("UAC_REQD_EXECS", BtPrefixPathname) reg.pkglistbl3("UNLIMIT_RESOURCES", - enum("cputime datasize memorysize stacksize")) + enum("cputime datasize memorysize stacksize virtualsize")) reg.usr("UNPRIVILEGED_USER", BtUserGroupName) reg.usr("UNPRIVILEGED_GROUP", BtUserGroupName) reg.pkglist("UNWRAP_FILES", BtPathPattern) @@ -1767,6 +1767,7 @@ func (reg *VarTypeRegistry) Init(src *Pkgsrc) { reg.pkglist("X11_LDFLAGS", BtLdFlag) reg.sys("X11_PKGSRCDIR.*", BtPathname) reg.pkglist("XMKMF_FLAGS", BtShellWord) + reg.sys("_ULIMIT_CMD", BtShellCommands) reg.pkglist("_WRAP_EXTRA_ARGS.*", BtShellWord) reg.infralist("_VARGROUPS", BtIdentifierDirect) |