pkgsrc/pkgtools/pkglint/files/mklines_test.go
rillig d42d69e1a0 pkgtools/pkglint: update to 21.2.4
Changes since 21.2.3:

Fixed loading of the tool definitions.  This adds 76 warnings for
packages that use tools without adding them to USE_TOOLS.  It also fixes
the warning about gmake and Meson.
2021-08-14 08:19:49 +00:00

1531 lines
41 KiB
Go

package pkglint
import (
"gopkg.in/check.v1"
"sort"
"strings"
)
func (s *Suite) Test_MkLines__quoting_LDFLAGS_for_GNU_configure(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
pkg := NewPackage(t.File("category/pkgbase"))
mklines := t.NewMkLinesPkg("Makefile", pkg, // XXX: Is in wrong directory.
MkCvsID,
"GNU_CONFIGURE=\tyes",
"CONFIGURE_ENV+=\tX_LIBS=${X11_LDFLAGS:Q}")
mklines.extraScope = &pkg.vars
mklines.Check()
t.CheckOutputLines(
"WARN: Makefile:3: Please use ${X11_LDFLAGS:M*:Q} instead of ${X11_LDFLAGS:Q}.")
}
func (s *Suite) Test_MkLines__for_loop_multiple_variables(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
t.SetUpTool("echo", "ECHO", AtRunTime)
t.SetUpTool("find", "FIND", AtRunTime)
t.SetUpTool("pax", "PAX", AtRunTime)
mklines := t.NewMkLines("Makefile", // From audio/squeezeboxserver
MkCvsID,
"",
"SBS_COPY=\tsource target",
"",
".for _list_ _dir_ in ${SBS_COPY}",
"\tcd ${WRKSRC} && ${FIND} ${${_list_}} -type f ! -name '*.orig' 2>/dev/null "+
"| pax -rw -pm ${DESTDIR}${PREFIX}/${${_dir_}}",
".endfor")
mklines.Check()
t.CheckOutputLines(
"WARN: Makefile:5: Variable names starting with an underscore (_list_) "+
"are reserved for internal pkgsrc use.",
"WARN: Makefile:5: Variable names starting with an underscore (_dir_) "+
"are reserved for internal pkgsrc use.",
"WARN: Makefile:6: The exitcode of \"${FIND}\" at the left of the | operator is ignored.")
}
func (s *Suite) Test_MkLines__comparing_YesNo_variable_to_string(c *check.C) {
t := s.Init(c)
t.SetUpPkgsrc()
t.Chdir(".")
t.FinishSetUp()
mklines := t.SetUpFileMkLines("databases/gdbm_compat/builtin.mk",
MkCvsID,
"",
".include \"../../mk/bsd.prefs.mk\"",
"",
".if ${USE_BUILTIN.gdbm} == \"no\"",
".endif",
".if ${USE_BUILTIN.gdbm:tu} == \"no\"", // Can never be true, since "no" is not uppercase.
".endif")
mklines.Check()
t.CheckOutputLines(
"WARN: databases/gdbm_compat/builtin.mk:5: " +
"USE_BUILTIN.gdbm should be matched against \"[yY][eE][sS]\" or \"[nN][oO]\", " +
"not compared with \"no\".")
}
func (s *Suite) Test_MkLines__varuse_sh_modifier(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
t.SetUpTool("sed", "SED", AfterPrefsMk)
mklines := t.NewMkLines("lang/qore/module.mk",
MkCvsID,
"qore-version=\tqore --short-version | ${SED} -e s/-.*//",
"PLIST_SUBST+=\tQORE_VERSION=\"${qore-version:sh}\"")
var vars2 []string
mklines.mklines[1].ForEachUsed(func(varUse *MkVarUse, time VucTime) {
vars2 = append(vars2, varUse.varname)
})
t.CheckDeepEquals(vars2, []string{"SED"})
var vars3 []string
mklines.mklines[2].ForEachUsed(func(varUse *MkVarUse, time VucTime) {
vars3 = append(vars3, varUse.varname)
})
// qore-version, despite its unusual name, is a pretty normal Make variable.
t.CheckDeepEquals(vars3, []string{"qore-version"})
mklines.Check()
// No warnings about defined but not used or vice versa
t.CheckOutputEmpty()
}
func (s *Suite) Test_MkLines_Check__unusual_target(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
t.SetUpTool("cc", "CC", AtRunTime)
mklines := t.NewMkLines("Makefile",
MkCvsID,
"",
"echo: echo.c",
"\tcc -o ${.TARGET} ${.IMPSRC}")
mklines.Check()
t.CheckOutputLines(
"WARN: Makefile:3: Undeclared target \"echo\".")
}
func (s *Suite) Test_MkLines_Check__loop_variable_used_outside_loop(c *check.C) {
t := s.Init(c)
t.SetUpTool("echo", "", AtRunTime)
mklines := t.NewMkLines("filename.mk",
MkCvsID,
"",
"do-install:",
"\techo ${msg}",
".for msg in message",
"\techo ${msg}",
".endfor")
mklines.Check()
t.CheckOutputLines(
"WARN: filename.mk:4: msg is used but not defined.")
}
// For parameterized variables, the "defined but not used" and
// the "used but not defined" checks are loosened a bit.
// When VAR.param1 is defined or used, VAR.param2 is also regarded
// as defined or used since often in pkgsrc, parameterized variables
// are not referred to by their exact names but by VAR.${param}.
func (s *Suite) Test_MkLines_Check__varuse_parameterized(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
mklines := t.NewMkLines("converters/wv2/Makefile",
MkCvsID,
"CONFIGURE_ARGS+=\t\t${CONFIGURE_ARGS.${ICONV_TYPE}-iconv}",
"CONFIGURE_ARGS.gnu-iconv=\t--with-libiconv=${BUILDLINK_PREFIX.iconv}")
mklines.Check()
// No warnings about CONFIGURE_ARGS.* being defined but not used or vice versa.
t.CheckOutputLines(
"WARN: converters/wv2/Makefile:2: ICONV_TYPE is used but not defined.")
}
// When an ODE runtime loop is used to expand variables to shell commands,
// pkglint only understands that there is a variable that is executed as
// shell command.
//
// In this example, GCONF_SCHEMAS is a list of filenames, but pkglint doesn't know this
// because there is no built-in rule saying *_SCHEMAS are filenames.
// If the variable name had been GCONF_SCHEMA_FILES, pkglint would know.
//
// As of November 2018, pkglint sees GCONF_SCHEMAS as being the shell command.
// It doesn't expand the @s@ loop to see what really happens.
//
// If it did that, it could notice that GCONF_SCHEMAS expands to a single shell command,
// and in that command INSTALL_DATA is used as the command for the first time,
// and as a regular command line argument in all other times.
// This combination is strange enough to warrant a warning.
//
// The bug here is the missing semicolon just before the @}.
//
// Pkglint could offer to either add the missing semicolon.
// Or, if it knows what INSTALL_DATA does, it could simply say that INSTALL_DATA
// can handle multiple files in a single invocation.
func (s *Suite) Test_MkLines_Check__loop_modifier(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
mklines := t.NewMkLines("chat/xchat/Makefile",
MkCvsID,
"GCONF_SCHEMAS=\tapps_xchat_url_handler.schemas",
"post-install:",
"\t${GCONF_SCHEMAS:@s@"+
"${INSTALL_DATA} ${WRKSRC}/src/common/dbus/${s} ${DESTDIR}${GCONF_SCHEMAS_DIR}/@}")
mklines.Check()
// Earlier versions of pkglint warned about a missing @ at the end.
t.CheckOutputEmpty()
}
func (s *Suite) Test_MkLines_Check__PKG_SKIP_REASON_depending_on_OPSYS(c *check.C) {
t := s.Init(c)
t.SetUpPkgsrc()
t.Chdir("category/package")
t.FinishSetUp()
mklines := t.NewMkLines("Makefile",
MkCvsID,
"",
".include \"../../mk/bsd.prefs.mk\"",
"",
"PKG_SKIP_REASON+=\t\"Fails everywhere\"",
".if ${OPSYS} == \"Cygwin\"",
"PKG_SKIP_REASON+=\t\"Fails on Cygwin\"",
".endif")
mklines.Check()
t.CheckOutputLines(
"NOTE: Makefile:7: Consider setting NOT_FOR_PLATFORM instead of PKG_SKIP_REASON depending on ${OPSYS}.")
}
func (s *Suite) Test_MkLines_Check__PKG_SKIP_REASON_depending_on_OPSYS_and_others(c *check.C) {
t := s.Init(c)
t.SetUpPkgsrc()
t.Chdir("category/package")
t.FinishSetUp()
mklines := t.NewMkLines("Makefile",
MkCvsID,
"",
".include \"../../mk/bsd.prefs.mk\"",
"",
"PKG_SKIP_REASON+=\t\"Fails everywhere\"",
".if ${OPSYS} == \"Cygwin\" && ${MACHINE_ARCH} == i386",
"PKG_SKIP_REASON+=\t\"Fails on Cygwin i386\"",
".endif")
mklines.Check()
t.CheckOutputLines(
"NOTE: Makefile:7: Consider setting NOT_FOR_PLATFORM instead of PKG_SKIP_REASON depending on ${OPSYS}.")
}
func (s *Suite) Test_MkLines_Check__use_list_variable_as_part_of_word(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
t.SetUpTool("tr", "", AtRunTime)
mklines := t.NewMkLines("converters/chef/Makefile",
MkCvsID,
"\tcd ${WRKSRC} && tr '\\r' '\\n' < ${DISTDIR}/${DIST_SUBDIR}/${DISTFILES} > chef.l")
mklines.Check()
t.CheckOutputLines(
"WARN: converters/chef/Makefile:2: The list variable DISTFILES should not be embedded in a word.")
}
func (s *Suite) Test_MkLines_Check__absolute_pathname_depending_on_OPSYS(c *check.C) {
t := s.Init(c)
t.SetUpPkgsrc()
t.Chdir(".")
t.FinishSetUp()
mklines := t.SetUpFileMkLines("games/heretic2-demo/Makefile",
MkCvsID,
"",
".include \"../../mk/bsd.prefs.mk\"",
"",
".if ${OPSYS} == \"DragonFly\"",
"TAR_CMD=\t/usr/bin/bsdtar",
".endif",
"TAR_CMD=\t/usr/bin/bsdtar",
"",
"do-extract:",
"\t${TAR_CMD}")
mklines.Check()
// No warning about an unknown shell command in line 3 since that line depends on OPSYS.
// Shell commands that are specific to an operating system are probably defined
// and used intentionally, so even commands that are not known tools are allowed.
t.CheckOutputLines(
"WARN: games/heretic2-demo/Makefile:8: Unknown shell command \"/usr/bin/bsdtar\".")
}
func (s *Suite) Test_MkLines_Check__indentation(c *check.C) {
t := s.Init(c)
t.SetUpPkgsrc()
t.Chdir("category/package")
t.FinishSetUp()
mklines := t.NewMkLines("options.mk",
MkCvsID,
"",
".include \"../../mk/bsd.prefs.mk\"",
"",
". if !defined(GUARD_MK)",
". if ${OPSYS} == ${OPSYS}",
". for i in ${FILES}",
". if !defined(GUARD2_MK)",
". else",
". endif",
". endfor",
". if ${COND1}",
". elif ${COND2}",
". else ${COND3}",
". endif",
". endif",
". endif",
". endif")
mklines.Check()
t.CheckOutputLines(
"NOTE: options.mk:5: This directive should be indented by 0 spaces.",
"WARN: options.mk:5: GUARD_MK is used but not defined.",
"NOTE: options.mk:6: This directive should be indented by 0 spaces.",
"NOTE: options.mk:7: This directive should be indented by 2 spaces.",
"WARN: options.mk:7: FILES is used but not defined.",
"NOTE: options.mk:8: This directive should be indented by 4 spaces.",
"WARN: options.mk:8: GUARD2_MK is used but not defined.",
"NOTE: options.mk:9: This directive should be indented by 4 spaces.",
"NOTE: options.mk:10: This directive should be indented by 4 spaces.",
"NOTE: options.mk:11: This directive should be indented by 2 spaces.",
"NOTE: options.mk:12: This directive should be indented by 2 spaces.",
"WARN: options.mk:12: COND1 is used but not defined.",
"NOTE: options.mk:13: This directive should be indented by 2 spaces.",
"WARN: options.mk:13: COND2 is used but not defined.",
"NOTE: options.mk:14: This directive should be indented by 2 spaces.",
"ERROR: options.mk:14: \".else\" does not take arguments. If you meant \"else if\", use \".elif\".",
"NOTE: options.mk:15: This directive should be indented by 2 spaces.",
"NOTE: options.mk:16: This directive should be indented by 0 spaces.",
"NOTE: options.mk:17: This directive should be indented by 0 spaces.",
"NOTE: options.mk:18: This directive should be indented by 0 spaces.",
"ERROR: options.mk:18: Unmatched .endif.")
}
// The .include directives do not need to be indented. They have the
// syntactical form of directives but cannot be nested in a single file.
// Therefore they may be either indented at the correct indentation depth
// or not indented at all.
func (s *Suite) Test_MkLines_Check__indentation_include(c *check.C) {
t := s.Init(c)
t.SetUpPkgsrc()
t.Chdir("category/package")
t.FinishSetUp()
t.CreateFileLines("included.mk")
mklines := t.SetUpFileMkLines("module.mk",
MkCvsID,
"",
".include \"../../mk/bsd.prefs.mk\"",
"",
".if ${PKGPATH} == \"category/nonexistent\"",
".include \"included.mk\"",
". include \"included.mk\"",
". include \"included.mk\"",
". include \"included.mk\"",
".endif")
mklines.Check()
t.CheckOutputLines(
"ERROR: module.mk:5: There is no package in \"../../category/nonexistent\".",
"NOTE: module.mk:7: This directive should be indented by 2 spaces.",
"NOTE: module.mk:9: This directive should be indented by 2 spaces.")
}
func (s *Suite) Test_MkLines_Check__unfinished_directives(c *check.C) {
t := s.Init(c)
t.SetUpPkgsrc()
t.Chdir("category/package")
t.FinishSetUp()
mklines := t.NewMkLines("opsys.mk",
MkCvsID,
"",
".include \"../../mk/bsd.prefs.mk\"",
"",
".for i in 1 2 3 4 5",
". if ${OPSYS} == NetBSD",
". if ${MACHINE_ARCH} == x86_64",
". if ${OS_VERSION:M8.*}")
mklines.Check()
t.CheckOutputLines(
"ERROR: opsys.mk:EOF: .if from line 8 must be closed.",
"ERROR: opsys.mk:EOF: .if from line 7 must be closed.",
"ERROR: opsys.mk:EOF: .if from line 6 must be closed.",
"ERROR: opsys.mk:EOF: .for from line 5 must be closed.")
}
func (s *Suite) Test_MkLines_Check__unbalanced_directives(c *check.C) {
t := s.Init(c)
t.SetUpPkgsrc()
t.Chdir("category/package")
t.FinishSetUp()
mklines := t.NewMkLines("opsys.mk",
MkCvsID,
"",
".include \"../../mk/bsd.prefs.mk\"",
"",
".for i in 1 2 3 4 5",
". if ${OPSYS} == NetBSD",
". endfor",
".endif")
mklines.Check()
// As of November 2018 pkglint doesn't find that the inner .if is closed by an .endfor.
// This is checked by bmake, though.
//
// As soon as pkglint starts to analyze .if/.for as regular statements
// like in most programming languages, it will find this inconsistency, too.
t.CheckOutputEmpty()
}
func (s *Suite) Test_MkLines_Check__incomplete_subst_at_end(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
mklines := t.NewMkLines("subst.mk",
MkCvsID,
"",
"SUBST_CLASSES+=\tclass")
mklines.Check()
t.CheckOutputLines(
"WARN: subst.mk:EOF: Missing SUBST block for \"class\".")
}
func (s *Suite) Test_MkLines_collectRationale(c *check.C) {
t := s.Init(c)
test := func(specs ...string) {
lines := mapStr(specs, func(s string) string { return s[4:] })
mklines := t.NewMkLines("filename.mk", lines...)
mklines.collectRationale()
var actual []string
mklines.ForEach(func(mkline *MkLine) {
actual = append(actual, condStr(mkline.HasRationale(), "R ", "- ")+mkline.Text)
})
t.CheckDeepEquals(actual, specs)
}
// An uncommented line does not have a rationale.
test(
"- VAR=value")
// The rationale can be given at the end of the line.
// This is useful for short explanations or remarks.
test(
"R VAR=value # rationale")
// A rationale can be given above a line. This is useful for
// explanations that don't fit into a single line.
test(
"R # rationale",
"R VAR=value")
// A commented variable assignment is just that, commented code.
// It does not count as a rationale.
test(
"- #VAR=value",
"- VAR=value")
// An empty line ends the rationale. The empty line does have a
// rationale itself, but that is useless since pkglint doesn't
// check empty lines for rationales.
test(
"R # rationale",
"R ",
"- VAR=value")
// A rationale covers all lines that follow, until the next empty
// line.
test(
"R # rationale",
"R NOT_FOR_PLATFORM+=\tLinux-*-*",
"R NOT_FOR_PLATFORM+=\tNetBSD-*-*",
"R NOT_FOR_PLATFORM+=\tCygwin-*-*")
// Large comment blocks often end with an empty comment line.
test(
"R # rationale",
"R #",
"R NOT_FOR_PLATFORM+=\tLinux-*-*",
"R NOT_FOR_PLATFORM+=\tNetBSD-*-*",
"R NOT_FOR_PLATFORM+=\tCygwin-*-*")
// The CVS Id is not a rationale.
test(
"- "+MkCvsID,
"- VAR=\tvalue")
// A rationale at the end of a line applies only to that line.
test(
"- VAR=\tvalue",
"R VAR=\tvalue # rationale",
"- VAR=\tvalue")
}
func (s *Suite) Test_MkLines_collectUsedVariables__simple(c *check.C) {
t := s.Init(c)
mklines := t.NewMkLines("filename.mk",
"\t${VAR}")
mkline := mklines.mklines[0]
mklines.collectUsedVariables()
t.Check(mklines.allVars.vs, check.HasLen, 1)
t.CheckEquals(mklines.allVars.create("VAR").used, mkline)
t.CheckEquals(mklines.allVars.FirstUse("VAR"), mkline)
}
func (s *Suite) Test_MkLines_collectUsedVariables__nested(c *check.C) {
t := s.Init(c)
mklines := t.NewMkLines("filename.mk",
MkCvsID,
"",
"LHS.${lparam}=\tRHS.${rparam}",
"",
"target:",
"\t${outer.${inner}}")
assignMkline := mklines.mklines[2]
shellMkline := mklines.mklines[5]
mklines.collectUsedVariables()
t.CheckEquals(len(mklines.allVars.vs), 5)
t.CheckEquals(mklines.allVars.FirstUse("lparam"), assignMkline)
t.CheckEquals(mklines.allVars.FirstUse("rparam"), assignMkline)
t.CheckEquals(mklines.allVars.FirstUse("inner"), shellMkline)
t.CheckEquals(mklines.allVars.FirstUse("outer.*"), shellMkline)
t.CheckEquals(mklines.allVars.FirstUse("outer.${inner}"), shellMkline)
}
func (s *Suite) Test_MkLines_collectDocumentedVariables(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
t.SetUpTool("rm", "RM", AtRunTime)
mklines := t.NewMkLines("Makefile",
MkCvsID,
"#",
"# Copyright 2000-2018",
"#",
"# This whole comment is ignored, until the next empty line.",
"# Since it contains the word \"copyright\", it's probably legalese",
"# instead of documentation.",
"",
"# User-settable variables:",
"#",
"# PKG_DEBUG_LEVEL",
"#\tHow verbose should pkgsrc be when running shell commands?",
"#",
"#\t* 0:\tdon't show most shell ...",
"",
"# PKG_VERBOSE",
"#\tWhen this variable is defined, pkgsrc gets a bit more verbose",
"#\t(i.e. \"-v\" option is passed to some commands ...",
"",
"# VARIABLE",
"#\tA paragraph of a single line is not enough to be recognized as \"relevant\".",
"",
"# PARAGRAPH",
"#\tA paragraph may end in a",
"#\tPARA_END_VARNAME.",
"",
"# VARBASE1.<param1>",
"# VARBASE2.*",
"# VARBASE3.${id}",
"",
"# NETBSD/amd64",
"#\tThis is not a variable name.",
"#\tThe slash must not appear in a variable name.",
"",
"# _____",
"#\tThis is not a variable name.",
"#\tVariable names must have at least one letter.")
// The variables that appear in the documentation are marked as
// both used and defined, to prevent the "defined but not used" warnings.
mklines.collectDocumentedVariables()
var varnames []string
mklines.allVars.forEach(func(varname string, data *scopeVar) {
if data.used != nil {
varnames = append(varnames, sprintf("%s (line %s)", varname, data.used.Linenos()))
}
})
sort.Strings(varnames)
expected := []string{
"PARAGRAPH (line 23)",
"PKG_DEBUG_LEVEL (line 11)",
"PKG_VERBOSE (line 16)",
"VARBASE1.* (line 27)",
"VARBASE2.* (line 28)",
"VARBASE3.* (line 29)"}
t.CheckDeepEquals(varnames, expected)
}
func (s *Suite) Test_MkLines_collectVariables(c *check.C) {
t := s.Init(c)
t.SetUpPkgsrc()
t.CreateFileLines("mk/tools/defaults.mk",
"USE_TOOLS+= autoconf autoconf213")
mklines := t.NewMkLines("determine-defined-variables.mk",
MkCvsID,
"",
"USE_TOOLS+=\t\tautoconf213 autoconf",
"",
"OPSYSVARS+=\t\tOSV",
"OSV.NetBSD=\t\tNetBSD-specific value",
"",
"SUBST_CLASSES+=\t\tsubst",
"SUBST_STAGE.subst=\tpre-configure",
"SUBST_FILES.subst=\tfile",
"SUBST_VARS.subst=\tSUV",
"SUV=\t\t\tvalue for substitution",
"",
"#BUILD_DEFS+=\t\tVARBASE",
"",
"pre-configure:",
"\t${RUN} autoreconf; autoheader-2.13",
"\t${ECHO} ${OSV:Q}")
t.FinishSetUp()
mklines.Check()
// The tools autoreconf and autoheader213 are known at this point because of the USE_TOOLS line.
// The SUV variable is used implicitly by the SUBST framework, therefore no warning.
// The OSV.NetBSD variable is used indirectly because OSV is declared
// as being OPSYS-specific, therefore no warning.
t.CheckOutputEmpty()
}
func (s *Suite) Test_MkLines_collectVariables__BUILTIN_FIND_FILES_VAR(c *check.C) {
t := s.Init(c)
t.SetUpPackage("category/package")
t.CreateFileLines("mk/buildlink3/bsd.builtin.mk",
MkCvsID)
mklines := t.SetUpFileMkLines("category/package/builtin.mk",
MkCvsID,
"",
"BUILTIN_FIND_FILES_VAR:=\tH_XFT2",
"BUILTIN_FIND_FILES.H_XFT2=\t${X11BASE}/include/X11/Xft/Xft.h",
"",
".include \"../../mk/buildlink3/bsd.builtin.mk\"",
"",
".if ${H_XFT2:N__nonexistent__} && ${H_UNDEF:N__nonexistent__}",
".endif")
t.FinishSetUp()
mklines.Check()
t.CheckOutputLines(
"WARN: ~/category/package/builtin.mk:8: H_UNDEF is used but not defined.")
}
func (s *Suite) Test_MkLines_collectVariables__no_tracing(c *check.C) {
t := s.Init(c)
mklines := t.SetUpFileMkLines("filename.mk",
MkCvsID,
"",
"BUILD_DEFS+=\tVAR1",
"PLIST_VARS+=\tvar2",
"SUBST_VARS.id+=\tVAR3")
t.DisableTracing()
mklines.collectVariables(false, false)
t.CheckOutputEmpty()
}
func (s *Suite) Test_MkLines_collectVariables__BUILD_DEFS(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
mklines := t.NewMkLines("filename.mk",
MkCvsID,
"",
"BUILD_DEFS+=\t\tBD_VAR",
// In practice, these variables are called *_GROUP.
"PKG_GROUPS_VARS+=\tGRP",
// In practice, these variables are called *_USER.
"PKG_USERS_VARS+=\tUSR")
mklines.Check()
t.CheckDeepEquals(
keys(mklines.buildDefs),
[]string{"BD_VAR", "GRP", "USR"})
}
func (s *Suite) Test_MkLines_collectVariables__find_files_and_headers(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
mklines := t.NewMkLines("builtin.mk",
MkCvsID,
"",
"BUILTIN_FIND_FILES_VAR=\t\tX1_FILE X2_FILE",
"BUILTIN_FIND_HEADERS_VAR=\tX1_HEADER X2_HEADER")
mklines.Check()
t.CheckDeepEquals(
keys(mklines.allVars.vs),
[]string{
"BUILTIN_FIND_FILES_VAR",
"BUILTIN_FIND_HEADERS_VAR",
"X1_FILE",
"X1_HEADER",
"X2_FILE",
"X2_HEADER"})
}
// Ensures that during MkLines.ForEach, the conditional variables in
// MkLines.Indentation are correctly updated for each line.
func (s *Suite) Test_MkLines_ForEach__conditional_variables(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
mklines := t.NewMkLines("conditional.mk",
MkCvsID,
"",
".if defined(PKG_DEVELOPER)",
"DEVELOPER=\tyes",
".endif",
"",
".if ${USE_TOOLS:Mgettext}",
"USES_GETTEXT=\tyes",
".endif")
seenDeveloper := false
seenUsesGettext := false
mklines.ForEach(func(mkline *MkLine) {
if mkline.IsVarassign() {
switch mkline.Varname() {
case "DEVELOPER":
t.CheckEquals(mklines.indentation.IsConditional(), true)
seenDeveloper = true
case "USES_GETTEXT":
t.CheckEquals(mklines.indentation.IsConditional(), true)
seenUsesGettext = true
}
}
})
t.CheckEquals(seenDeveloper, true)
t.CheckEquals(seenUsesGettext, true)
}
func (s *Suite) Test_MkLines_ForEachEnd(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
mklines := t.NewMkLines("filename.mk",
MkCvsID)
// Calls to MkLines.ForEach cannot nest since they modify fields
// in the MkLines type. As of December 2019 there is no separation
// between:
// - The MkLines as a collection of data
// - An iterator over the MkLines
// - The MkLinesChecker
t.ExpectAssert(func() {
mklines.ForEach(func(mkline *MkLine) {
mklines.ForEach(func(mkline *MkLine) {
})
})
})
}
func (s *Suite) Test_MkLines_checkAll__collect_else(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
mklines := t.NewMkLines("module.mk",
MkCvsID,
"",
".if 0",
".endif",
"",
".if 0",
".else",
".endif",
"",
".if 0",
".elif 0",
".endif")
// As a side effect of MkLines.ForEach,
// the HasElseBranch in the lines is updated.
mklines.collectVariables(false, false)
t.CheckEquals(mklines.mklines[2].HasElseBranch(), false)
t.CheckEquals(mklines.mklines[5].HasElseBranch(), true)
t.CheckEquals(mklines.mklines[9].HasElseBranch(), false)
}
// Demonstrates how to define your own make(1) targets for creating
// files in the current directory. The pkgsrc-wip category Makefile
// does this, while all other categories don't need any custom code.
func (s *Suite) Test_MkLines_checkAll__wip_category_Makefile(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("-Wall", "--explain")
t.SetUpVartypes()
t.SetUpTool("rm", "RM", AtRunTime)
t.CreateFileLines("mk/misc/category.mk")
mklines := t.SetUpFileMkLines("wip/Makefile",
MkCvsID,
"",
"COMMENT=\tWIP pkgsrc packages",
"",
"SUBDIR+=\taaa",
"SUBDIR+=\tzzz",
"",
"${.CURDIR}/PKGDB:",
"\t${RM} -f ${.CURDIR}/PKGDB",
"",
"${.CURDIR}/INDEX:",
"\t${RM} -f ${.CURDIR}/INDEX",
"",
"clean-tmpdir:",
"\t${RUN} rm -rf tmpdir",
"",
".include \"../mk/misc/category.mk\"")
mklines.Check()
t.CheckOutputLines(
"WARN: ~/wip/Makefile:14: Undeclared target \"clean-tmpdir\".",
"",
"\tTo define a custom target in a package, declare it like this:",
"",
"\t\t.PHONY: my-target",
"",
"\tTo define a custom target that creates a file (should be rarely",
"\tneeded), declare it like this:",
"",
"\t\t${.CURDIR}/my-file:",
"")
}
func (s *Suite) Test_MkLines_checkAll__undocumented_options(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
t.SetUpOption("known", "")
mklines := t.NewMkLines("options.mk",
MkCvsID,
"#",
"PKG_OPTIONS_VAR=\tPKG_OPTIONS.pkgbase",
"PKG_SUPPORTED_OPTIONS=\tknown unknown",
"PKG_SUGGESTED_OPTIONS=\tknown unknown")
mklines.Check()
t.CheckOutputLines(
"WARN: options.mk:4: Undocumented option \"unknown\".")
}
func (s *Suite) Test_MkLines_checkAll__PLIST_VARS(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
t.SetUpOption("both", "")
t.SetUpOption("only-added", "")
t.SetUpOption("only-defined", "")
t.CreateFileLines("mk/bsd.options.mk")
mklines := t.SetUpFileMkLines("category/package/options.mk",
MkCvsID,
"",
"PKG_OPTIONS_VAR=\tPKG_OPTIONS.pkg",
"PKG_SUPPORTED_OPTIONS=\tboth only-added only-defined",
"PKG_SUGGESTED_OPTIONS=\t# none",
"",
".include \"../../mk/bsd.options.mk\"",
"",
"PLIST_VARS+=\t\tboth only-added",
"",
".if !empty(PKG_OPTIONS:Mboth)",
"PLIST.both=\t\tyes",
".endif",
"",
".if !empty(PKG_OPTIONS:Monly-defined)",
"PLIST.only-defined=\tyes",
".endif")
mklines.Check()
t.CheckOutputLines(
"WARN: ~/category/package/options.mk:9: \"only-added\" is added to PLIST_VARS, but PLIST.only-added is not defined in this file.",
"WARN: ~/category/package/options.mk:16: PLIST.only-defined is defined, but \"only-defined\" is not added to PLIST_VARS in this file.")
}
func (s *Suite) Test_MkLines_checkAll__PLIST_VARS_indirect(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
t.SetUpOption("option1", "")
t.SetUpOption("option2", "")
mklines := t.SetUpFileMkLines("module.mk",
MkCvsID,
"",
"MY_PLIST_VARS=\toption1 option2",
"PLIST_VARS+=\t${MY_PLIST_VARS}",
".for option in option3",
"PLIST_VARS+=\t${option}",
".endfor",
"",
".if 0",
"PLIST.option1=\tyes",
".endif",
"",
".if 1",
"PLIST.option2=\tyes",
".endif")
mklines.Check()
// As of November 2018, pkglint doesn't analyze the .if 0 block.
// Therefore it doesn't know that the option1 block will never match because of the 0.
// This is ok though since it could be a temporary workaround from the package maintainer.
//
// As of November 2018, pkglint doesn't analyze the .for loop.
// Therefore it doesn't know that an .if block for option3 is missing.
t.CheckOutputEmpty()
}
func (s *Suite) Test_MkLines_checkAll__PLIST_VARS_indirect_2(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
t.SetUpOption("a", "")
t.SetUpOption("b", "")
t.SetUpOption("c", "")
mklines := t.NewMkLines("module.mk",
MkCvsID,
"",
"PKG_SUPPORTED_OPTIONS=\ta b c",
"PLIST_VARS+=\t\t${PKG_SUPPORTED_OPTIONS:S,a,,g}",
"",
"PLIST_VARS+=\t\tonly-added",
"",
"PLIST.only-defined=\tyes")
mklines.Check()
// If the PLIST_VARS contain complex expressions that involve other variables,
// it becomes too difficult for pkglint to decide whether the IDs can still match.
// Therefore, in such a case, no diagnostics are logged at all.
t.CheckOutputEmpty()
}
func (s *Suite) Test_MkLines_checkAll__defined_and_used_variables(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
mklines := t.NewMkLines("module.mk",
MkCvsID,
"",
".for lang in de fr",
"PLIST_VARS+=\t${lang}",
".endif",
"",
".for language in de fr",
"PLIST.${language}=\tyes",
".endif",
"",
"PLIST.other=\tyes")
mklines.Check()
// If there are variable involved in the definition of PLIST_VARS or PLIST.*,
// it becomes too difficult for pkglint to decide whether the IDs can still match.
// Therefore, in such a case, no diagnostics are logged at all.
t.CheckOutputEmpty()
}
func (s *Suite) Test_MkLines_checkAll__hacks_mk(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
mklines := t.NewMkLines("hacks.mk",
MkCvsID,
"",
"PKGNAME?=\tpkgbase-1.0")
mklines.Check()
// No warning about including bsd.prefs.mk before using the ?= operator.
// This is because the hacks.mk files are included implicitly by the
// pkgsrc infrastructure right after bsd.prefs.mk.
t.CheckOutputEmpty()
}
func (s *Suite) Test_MkLines_checkAll__MASTER_SITE_in_HOMEPAGE(c *check.C) {
t := s.Init(c)
t.SetUpMasterSite("MASTER_SITE_GITHUB", "https://github.com/")
t.SetUpVartypes()
mklines := t.NewMkLines("devel/catch/Makefile",
MkCvsID,
"HOMEPAGE=\t${MASTER_SITE_GITHUB:=philsquared/Catch/}",
"HOMEPAGE=\t${MASTER_SITE_GITHUB}",
"HOMEPAGE=\t${MASTER_SITES}",
"HOMEPAGE=\t${MASTER_SITES}${GITHUB_PROJECT}")
mklines.Check()
t.CheckOutputLines(
"WARN: devel/catch/Makefile:2: HOMEPAGE should not be defined in terms of MASTER_SITEs. "+
"Use https://github.com/philsquared/Catch/ directly.",
"WARN: devel/catch/Makefile:3: HOMEPAGE should not be defined in terms of MASTER_SITEs. "+
"Use https://github.com/ directly.",
"WARN: devel/catch/Makefile:4: HOMEPAGE should not be defined in terms of MASTER_SITEs.",
"WARN: devel/catch/Makefile:5: HOMEPAGE should not be defined in terms of MASTER_SITEs.")
}
// Up to June 2019, pkglint wrongly replaced the HOMEPAGE
// with an empty string.
func (s *Suite) Test_MkLines_checkAll__autofix_MASTER_SITE_in_HOMEPAGE(c *check.C) {
t := s.Init(c)
test := func(diagnostics ...string) {
mklines := t.SetUpFileMkLines("Makefile",
MkCvsID,
"",
"MASTER_SITES= \\",
"\thttps://cdn1.example.org/ \\",
"\thttps://cdn2.example.org/",
"",
"HOMEPAGE=\t${MASTER_SITES}")
mklines.Check()
t.CheckOutput(diagnostics)
}
t.SetUpVartypes()
t.Chdir(".")
t.SetUpCommandLine("-Wall")
test(
"WARN: Makefile:7: HOMEPAGE should not be defined in terms of MASTER_SITEs.")
t.SetUpCommandLine("-Wall", "--autofix")
test(
nil...)
}
func (s *Suite) Test_MkLines_checkAll__autofix_MASTER_SITE_in_HOMEPAGE_in_package(c *check.C) {
t := s.Init(c)
t.SetUpPackage("category/package",
"MASTER_SITES=\thttps://cdn1.example.org/ https://cdn2.example.org/",
"HOMEPAGE=\t${MASTER_SITES}")
t.Main("-Wall", "-q", "category/package")
// When MASTER_SITES consists of several URLs, take the first one,
// assuming that it is the most appropriate.
t.CheckOutputLines(
"WARN: ~/category/package/Makefile:9: " +
"HOMEPAGE should not be defined in terms of MASTER_SITEs. " +
"Use https://cdn1.example.org/ directly.")
t.Main("-Wall", "-q", "--autofix", "category/package")
t.CheckOutputLines(
"AUTOFIX: ~/category/package/Makefile:9: " +
"Replacing \"${MASTER_SITES}\" " +
"with \"https://cdn1.example.org/\".")
}
func (s *Suite) Test_MkLines_checkAll__VERSION_as_word_part_in_MASTER_SITES(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
t.SetUpMasterSite("MASTER_SITE_SOURCEFORGE", "https://download.sf.net/")
mklines := t.NewMkLines("geography/viking/Makefile",
MkCvsID,
"MASTER_SITES=\t${MASTER_SITE_SOURCEFORGE:=viking/}${VERSION}/")
mklines.Check()
t.CheckOutputLines(
"WARN: geography/viking/Makefile:2: "+
"The list variable MASTER_SITE_SOURCEFORGE should not be embedded in a word.",
"WARN: geography/viking/Makefile:2: VERSION is used but not defined.")
}
func (s *Suite) Test_MkLines_checkAll__shell_command_as_word_part_in_ENV_list(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
mklines := t.NewMkLines("x11/lablgtk1/Makefile",
MkCvsID,
"CONFIGURE_ENV+=\tCC=${CC}")
mklines.Check()
t.CheckOutputLines(
"WARN: x11/lablgtk1/Makefile:2: Please use ${CC:Q} instead of ${CC}.")
}
func (s *Suite) Test_MkLines_checkAll__extra_warnings(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("-Wextra")
t.SetUpVartypes()
pkg := NewPackage(t.File("category/pkgbase"))
mklines := t.NewMkLinesPkg("options.mk", pkg,
MkCvsID,
"",
".for word in ${PKG_FAIL_REASON}",
"CONFIGURE_ARGS+=\t--sharedir=${PREFIX}/share/kde",
"COMMENT=\t\t# defined",
".endfor",
"GAMES_USER?=\t\tpkggames",
"GAMES_GROUP?=\t\tpkggames",
"PLIST_SUBST+=\t\tCONDITIONAL=${CONDITIONAL}",
"CONDITIONAL=\t\t\"@comment\"",
"BUILD_DIRS=\t\t${WRKSRC}/../build")
mklines.Check()
t.CheckOutputLines(
"NOTE: options.mk:5: Please use \"# empty\", \"# none\" or \"# yes\" instead of \"# defined\".",
"WARN: options.mk:7: Please include \"../../mk/bsd.prefs.mk\" before using \"?=\".",
"WARN: options.mk:11: Building the package should take place entirely inside ${WRKSRC}, not \"${WRKSRC}/..\".",
"NOTE: options.mk:11: You can use \"../build\" instead of \"${WRKSRC}/../build\".")
}
// Between 2019-12-31 and 2020-01-01, pkglint panicked because it didn't
// expect that a package would define PKGDIR to point to itself.
func (s *Suite) Test_MkLines_checkAll__assertion(c *check.C) {
t := s.Init(c)
pkg := NewPackage(t.SetUpPackage("category/package",
"PKGDIR=\t../../category/package"))
t.FinishSetUp()
pkg.Check()
t.CheckOutputEmpty()
}
// At 2018-12-02, pkglint had resolved ${MY_PLIST_VARS} into a single word,
// whereas the correct behavior is to resolve it into two words.
// It had produced warnings about mismatched PLIST_VARS IDs.
func (s *Suite) Test_MkLines_checkVarassignPlist__indirect(c *check.C) {
t := s.Init(c)
t.SetUpVartypes()
mklines := t.SetUpFileMkLines("plist.mk",
MkCvsID,
"",
"MY_PLIST_VARS=\tvar1 var2",
"PLIST_VARS+=\t${MY_PLIST_VARS}",
"",
"PLIST.var1=\tyes",
"PLIST.var2=\tyes")
mklines.Check()
t.CheckOutputEmpty()
}
func (s *Suite) Test_MkLines_CheckUsedBy__show_autofix(c *check.C) {
t := s.Init(c)
t.SetUpCommandLine("--show-autofix")
test := func(pkgpath PkgsrcPath, lines []string, diagnostics []string) {
mklines := t.NewMkLines("Makefile.common", lines...)
mklines.CheckUsedBy(pkgpath)
t.CheckOutput(diagnostics)
}
lines := func(lines ...string) []string { return lines }
diagnostics := func(diagnostics ...string) []string { return diagnostics }
// This file is too short to be checked.
test(
"category/package",
lines(),
diagnostics())
// Still too short.
test(
"category/package",
lines(
MkCvsID),
diagnostics())
// Still too short.
test(
"category/package",
lines(
MkCvsID,
""),
diagnostics())
// This file is correctly mentioned.
test(
"sysutils/mc",
lines(
MkCvsID,
"",
"# used by sysutils/mc"),
diagnostics())
// This file is not correctly mentioned, therefore the line is inserted.
test(
"category/package",
lines(
MkCvsID,
"",
"VARNAME=\tvalue"),
diagnostics(
"WARN: Makefile.common:1: Please add a line \"# used by category/package\" here.",
"AUTOFIX: Makefile.common:1: Inserting a line \"# used by category/package\" below this line."))
// The "used by" comments may either start in line 2 or in line 3.
test(
"category/package",
lines(
MkCvsID,
"#",
"#"),
diagnostics(
"WARN: Makefile.common:1: Please add a line \"# used by category/package\" here.",
"AUTOFIX: Makefile.common:1: Inserting a line \"# used by category/package\" below this line."))
// TODO: What if there is an introductory comment first? That should stay at the top of the file.
// TODO: What if the "used by" comments appear in the second paragraph, preceded by only comments and empty lines?
// Since the first paragraph already has some comments, the "used by"
// comments need their separate paragraph, which is inserted after
// the first paragraph.
test("category/package",
lines(
MkCvsID,
"# A normal comment",
"# that spans",
"# several lines"),
diagnostics(
"AUTOFIX: Makefile.common:4: Inserting a line \"\" below this line.",
"WARN: Makefile.common:4: Please add a line \"# used by category/package\" here.",
"AUTOFIX: Makefile.common:4: Inserting a line \"# used by category/package\" below this line."))
t.CheckEquals(G.Logger.autofixAvailable, true)
}
func (s *Suite) Test_MkLines_CheckUsedBy(c *check.C) {
t := s.Init(c)
test := func(pkgpath PkgsrcPath, lines []string, diagnostics []string) {
mklines := t.NewMkLines("Makefile.common", lines...)
mklines.CheckUsedBy(pkgpath)
t.CheckOutput(diagnostics)
}
lines := func(lines ...string) []string { return lines }
diagnostics := func(diagnostics ...string) []string { return diagnostics }
// The including package is already mentioned in the single "used by"
// paragraph. Nothing needs to be changed.
test("category/package2/Makefile",
lines(
MkCvsID,
"# This Makefile fragment is",
"# used by category/package1/Makefile, as well as", // looks similar to the formal "used by".
"# some others.",
"",
"# used by category/package2/Makefile"),
diagnostics())
// The including file is not yet mentioned. There is a single "used by"
// paragraph, and the including file needs to be added to that paragraph.
// It is added in the correct sorting order. The entries are simply
// sorted alphabetically.
test("category/package/Makefile",
lines(
MkCvsID,
"# This Makefile fragment is",
"# used by category/package1/Makefile, as well as", // looks similar to the formal "used by".
"# some others.",
"",
"# used by category/package2/Makefile"),
diagnostics(
"WARN: Makefile.common:6: Please add a line \"# used by category/package/Makefile\" here."))
// There are two separate paragraphs with "used by" lines. The first of
// them is the interesting one. The new line is added to the first paragraph.
test("category/package",
lines(
MkCvsID,
"# used by category/package1",
"",
"# used by category/package2"),
diagnostics(
"WARN: Makefile.common:4: There should only be a single \"used by\" paragraph per file.",
"WARN: Makefile.common:2: Please add a line \"# used by category/package\" here."))
// The empty comment also separates the two paragraphs, like in the
// previous test case.
test("category/package",
lines(
MkCvsID,
"# used by category/package1",
"#",
"# used by category/package2"),
diagnostics(
"WARN: Makefile.common:4: There should only be a single \"used by\" paragraph per file.",
"WARN: Makefile.common:2: Please add a line \"# used by category/package\" here."))
// Code coverage for hasOther being true and conflict being non-nil.
// Ensures that the warning is printed in the first wrong line.
test("category/package",
lines(
MkCvsID,
"",
"# Unrelated comment.",
"# used by category/package1",
"# used by category/package2"),
diagnostics(
"WARN: Makefile.common:4: The \"used by\" lines should be in a separate paragraph.",
"WARN: Makefile.common:1: Please add a line \"# used by category/package\" here."))
// Code coverage for hasUsedBy being true and conflict being non-nil.
// Ensures that the warning is printed in the first wrong line.
test("category/package",
lines(
MkCvsID,
"",
"# used by category/package1",
"# Unrelated comment.",
"# Unrelated comment 2."),
diagnostics(
"WARN: Makefile.common:4: The \"used by\" lines should be in a separate paragraph.",
"WARN: Makefile.common:1: Please add a line \"# used by category/package\" here."))
t.CheckEquals(G.Logger.autofixAvailable, true)
}
func (s *Suite) Test_MkLines_CheckUsedBy__separate_paragraph(c *check.C) {
t := s.Init(c)
mklines := t.NewMkLines("Makefile.common",
MkCvsID,
"# a comment",
"# used by category/package",
"# a comment")
mklines.CheckUsedBy("category/package")
t.CheckOutputLines(
"WARN: Makefile.common:3: The \"used by\" lines should be in a separate paragraph.")
}
func (s *Suite) Test_MkLines_SplitToParagraphs(c *check.C) {
t := s.Init(c)
type lineRange struct {
from, to int
}
test := func(mklines *MkLines, ranges ...lineRange) {
paras := mklines.SplitToParagraphs()
var exp []*Paragraph
for _, r := range ranges {
exp = append(exp, NewParagraph(mklines, r.from, r.to))
}
t.CheckDeepEquals(paras, exp)
}
para := func(from, to int) lineRange { return lineRange{from, to} }
test(
t.NewMkLines("filename.mk",
MkCvsID,
"",
"# paragraph 2",
"#",
"VAR=\tstill paragraph 2",
"",
"# paragraph 3",
"#",
"# paragraph 4"),
para(0, 1),
para(2, 5),
para(6, 7),
para(8, 9))
test(
t.NewMkLines("filename.mk",
""),
nil...)
// Test coverage for i == 0.
test(
t.NewMkLines("filename.mk",
"#"),
nil...)
// The empty comment line is not a paragraph separator. To be a
// separator, it would have to be enclosed by comment lines.
test(
t.NewMkLines("filename.mk",
"VAR=\tvalue",
"#"),
para(0, 2))
// The empty comment line is not a paragraph separator because
// below it there is no comment. This is a typical way of separating
// a multi-line comment from a variable definition.
test(
t.NewMkLines("filename.mk",
"# This comment spans",
"# multiple lines.",
"#",
"VAR=\tvalue"),
para(0, 4))
}
func (s *Suite) Test_MkLines_ExpandLoopVar(c *check.C) {
t := s.Init(c)
mklines := t.NewMkLines("filename.mk",
MkCvsID,
"",
".for file in a b c d e f g h",
". for rank in 1 2 3 4 5 6 7 8",
"CHESS_BOARD+=\t${file}${rank}",
". endfor",
".endfor")
var files []string
var ranks []string
var diagonals []string
mklines.ForEach(func(mkline *MkLine) {
if mkline.IsVarassign() {
ranks = mklines.ExpandLoopVar("rank")
files = mklines.ExpandLoopVar("file")
diagonals = mklines.ExpandLoopVar("diagonals")
}
})
t.CheckDeepEquals(files, strings.Split("abcdefgh", ""))
t.CheckDeepEquals(ranks, strings.Split("12345678", ""))
t.Check(diagonals, check.HasLen, 0)
}
func (s *Suite) Test_MkLines_ExpandLoopVar__multi(c *check.C) {
t := s.Init(c)
mklines := t.NewMkLines("filename.mk",
MkCvsID,
"",
".if 1",
". for key value in 1 one 2 two 3 three",
"VAR.${key}=\t${value}",
". endfor",
".endif")
var keys []string
var values []string
mklines.ForEach(func(mkline *MkLine) {
if mkline.IsVarassign() {
keys = mklines.ExpandLoopVar("key")
values = mklines.ExpandLoopVar("value")
}
})
// As of June 2019, multi-variable .for loops are not yet implemented.
t.Check(keys, check.HasLen, 0)
t.Check(values, check.HasLen, 0)
}
func (s *Suite) Test_MkLines_ExpandLoopVar__malformed_for(c *check.C) {
t := s.Init(c)
mklines := t.NewMkLines("filename.mk",
MkCvsID,
"",
".for var in",
"VAR=\t${var}",
".endfor")
var values = []string{"uninitialized"}
mklines.ForEach(func(mkline *MkLine) {
if mkline.IsVarassign() {
values = mklines.ExpandLoopVar("key")
}
})
t.Check(values, check.HasLen, 0)
}