├── .gitignore
├── .hgignore
├── LICENSE
├── README
├── make.py
├── mkformat.py
├── mkparse.py
├── pymake
├── __init__.py
├── builtins.py
├── command.py
├── data.py
├── errors.py
├── functions.py
├── globrelative.py
├── implicit.py
├── parser.py
├── parserdata.py
├── process.py
├── util.py
└── win32process.py
└── tests
├── automatic-variables.mk
├── bad-command-continuation.mk
├── call.mk
├── cmd-stripdotslash.mk
├── cmdgoals.mk
├── commandmodifiers.mk
├── comment-parsing.mk
├── continuations-in-functions.mk
├── datatests.py
├── default-goal-set-first.mk
├── default-goal.mk
├── default-target.mk
├── default-target2.mk
├── define-directive.mk
├── depfailed.mk
├── depfailedj.mk
├── diamond-deps.mk
├── dotslash-dir.mk
├── dotslash-parse.mk
├── dotslash-phony.mk
├── dotslash.mk
├── doublecolon-exists.mk
├── doublecolon-priordeps.mk
├── doublecolon-remake.mk
├── dynamic-var.mk
├── empty-arg.mk
├── empty-command-semicolon.mk
├── empty-rule.mk
├── empty-with-deps.mk
├── env-var-append.mk
├── env-var-append2.mk
├── eof-continuation.mk
├── escape-chars.mk
├── escaped-continuation.mk
├── eval-duringexecute.mk
├── eval.mk
├── exit-code.mk
├── file-functions-symlinks.mk
├── file-functions.mk
├── foreach-local-variable.mk
├── formattingtests.py
├── func-refs.mk
├── functions.mk
├── functiontests.py
├── glob1.inc
├── glob2.inc
├── if-syntaxerr.mk
├── ifdefs-nesting.mk
├── ifdefs.mk
├── ignore-error.mk
├── implicit-chain.mk
├── implicit-dir.mk
├── implicit-terminal.mk
├── implicitsubdir.mk
├── include-dynamic.mk
├── include-file.inc
├── include-glob.mk
├── include-missing.mk
├── include-notfound.mk
├── include-optional-warning.mk
├── include-regen.mk
├── include-regen2.mk
├── include-regen3.mk
├── include-required-fails.mk
├── include-test.mk
├── includedeps-norebuild.mk
├── includedeps-sideeffects.mk
├── includedeps-stripdotslash.deps
├── includedeps-stripdotslash.mk
├── includedeps-variables.deps
├── includedeps-variables.mk
├── includedeps.deps
├── includedeps.mk
├── info.mk
├── justprint-native.mk
├── justprint.mk
├── keep-going-doublecolon.mk
├── keep-going-parallel.mk
├── keep-going.mk
├── line-continuations.mk
├── link-search.mk
├── makeflags.mk
├── matchany.mk
├── matchany2.mk
├── matchany3.mk
├── mkdir-fail.mk
├── mkdir.mk
├── multiple-rules-prerequisite-merge.mk
├── native-command-delay-load.mk
├── native-command-raise.mk
├── native-command-return-fail1.mk
├── native-command-return-fail2.mk
├── native-command-return.mk
├── native-command-shell-glob.mk
├── native-command-sys-exit-fail1.mk
├── native-command-sys-exit-fail2.mk
├── native-command-sys-exit.mk
├── native-environment.mk
├── native-pycommandpath-sep.mk
├── native-pycommandpath.mk
├── native-simple.mk
├── native-touch.mk
├── newlines.mk
├── no-remake.mk
├── nosuchfile.mk
├── notargets.mk
├── notparallel.mk
├── oneline-command-continuations.mk
├── override-propagate.mk
├── parallel-dep-resolution.mk
├── parallel-dep-resolution2.mk
├── parallel-native.mk
├── parallel-simple.mk
├── parallel-submake.mk
├── parallel-toserial.mk
├── parallel-waiting.mk
├── parentheses.mk
├── parsertests.py
├── path-length.mk
├── pathdir
├── pathtest
├── pathtest.exe
└── src
│ ├── Makefile
│ └── pathtest.cpp
├── patsubst.mk
├── phony.mk
├── pycmd.py
├── recursive-set.mk
├── recursive-set2.mk
├── remake-mtime.mk
├── rm-fail.mk
├── rm.mk
├── runtests.py
├── serial-dep-resolution.mk
├── serial-doublecolon-execution.mk
├── serial-rule-execution.mk
├── serial-rule-execution2.mk
├── serial-toparallel.mk
├── shellfunc.mk
├── simple-makeflags.mk
├── sort.mk
├── specified-target.mk
├── static-pattern.mk
├── static-pattern2.mk
├── subdir
├── delayload.py
├── pymod.py
└── testmodule.py
├── submake-path.makefile2
├── submake-path.mk
├── submake.makefile2
├── submake.mk
├── subprocess-path.mk
├── tab-intro.mk
├── target-specific.mk
├── unexport.mk
├── unexport.submk
├── unterminated-dollar.mk
├── var-change-flavor.mk
├── var-commandline.mk
├── var-overrides.mk
├── var-ref.mk
├── var-set.mk
├── var-substitutions.mk
├── vpath-directive-dynamic.mk
├── vpath-directive.mk
├── vpath.mk
├── vpath2.mk
├── wildcards.mk
└── windows-paths.mk
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | tests/*makelog
3 | .ropeproject/
4 |
--------------------------------------------------------------------------------
/.hgignore:
--------------------------------------------------------------------------------
1 | \.pyc$
2 | \.pyo$
3 | tests/.*.(py|g)makelog$
4 | ~$
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2009 The Mozilla Foundation
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a
6 | copy of this software and associated documentation files (the "Software"),
7 | to deal in the Software without restriction, including without limitation
8 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 | and/or sell copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 | DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | INTRODUCTION
2 |
3 | make.py (and the pymake modules that support it) are an implementation of the make tool
4 | which are mostly compatible with makefiles written for GNU make.
5 |
6 | PURPOSE
7 |
8 | The Mozilla project inspired this tool with several goals:
9 |
10 | * Improve build speeds, especially on Windows. This can be done by reducing the total number
11 | of processes that are launched, especially MSYS shell processes which are expensive.
12 |
13 | * Allow writing some complicated build logic directly in Python instead of in shell.
14 |
15 | * Allow computing dependencies for special targets, such as members within ZIP files.
16 |
17 | * Enable experiments with build system. By writing a makefile parser, we can experiment
18 | with converting in-tree makefiles to another build system, such as SCons, waf, ant, ...insert
19 | your favorite build tool here. Or we could experiment along the lines of makepp, keeping
20 | our existing makefiles, but change the engine to build a global dependency graph.
21 |
22 | KNOWN INCOMPATIBILITIES
23 |
24 | * Order-only prerequisites are not yet supported
25 |
26 | * Secondary expansion is not yet supported.
27 |
28 | * Target-specific variables behave differently than in GNU make: in pymake, the target-specific
29 | variable only applies to the specific target that is mentioned, and does not apply recursively
30 | to all dependencies which are remade. This is an intentional change: the behavior of GNU make
31 | is neither deterministic nor intuitive.
32 |
33 | * $(eval) is only supported during the parse phase. Any attempt to recursively expand
34 | an $(eval) function during command execution will fail. This is an intentional incompatibility.
35 |
36 | * There is a subtle difference in execution order that can cause unexpected changes in the
37 | following circumstance:
38 | ** A file `foo.c` exists on the VPATH
39 | ** A rule for `foo.c` exists with a dependency on `tool` and no commands
40 | ** `tool` is remade for some other reason earlier in the file
41 | In this case, pymake resets the VPATH of `foo.c`, while GNU make does not. This shouldn't
42 | happen in the real world, since a target found on the VPATH without commands is silly. But
43 | mozilla/js/src happens to have a rule, which I'm patching.
44 |
45 | * pymake does not implement any of the builtin implicit rules or the related variables. Mozilla
46 | only cares because pymake doesn't implicitly define $(RM), which I'm also fixing in the Mozilla
47 | code.
48 |
49 | ISSUES
50 |
51 | * Speed is a problem.
52 |
53 | FUTURE WORK
54 |
55 | * implement a new type of command which is implemented in python. This would allow us
56 | to replace the current `nsinstall` binary (and execution costs for the shell and binary) with an
57 | in-process python solution.
58 |
59 | AUTHOR
60 |
61 | Initial code was written by Benjamin Smedberg . For future releases see
62 | http://benjamin.smedbergs.us/pymake/
63 |
64 | See the LICENSE file for license information (MIT license)
65 |
--------------------------------------------------------------------------------
/make.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """
4 | make.py
5 |
6 | A drop-in or mostly drop-in replacement for GNU make.
7 | """
8 |
9 | import sys, os
10 | import pymake.command, pymake.process
11 |
12 | import gc
13 |
14 | if __name__ == '__main__':
15 | if sys.version_info < (3,0):
16 | sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
17 | sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0)
18 | else:
19 | # Unbuffered text I/O is not allowed in Python 3.
20 | sys.stdout = os.fdopen(sys.stdout.fileno(), 'w')
21 | sys.stderr = os.fdopen(sys.stderr.fileno(), 'w')
22 |
23 | gc.disable()
24 |
25 | pymake.command.main(sys.argv[1:], os.environ, os.getcwd(), cb=sys.exit)
26 | pymake.process.ParallelContext.spin()
27 | assert False, "Not reached"
28 |
--------------------------------------------------------------------------------
/mkformat.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import sys
4 | import pymake.parser
5 |
6 | filename = sys.argv[1]
7 | source = None
8 |
9 | with open(filename, 'rU') as fh:
10 | source = fh.read()
11 |
12 | statements = pymake.parser.parsestring(source, filename)
13 | print(statements.to_source())
14 |
--------------------------------------------------------------------------------
/mkparse.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import sys
4 | import pymake.parser
5 |
6 | for f in sys.argv[1:]:
7 | print("Parsing %s" % f)
8 | fd = open(f, 'rU')
9 | s = fd.read()
10 | fd.close()
11 | stmts = pymake.parser.parsestring(s, f)
12 | print(stmts)
13 |
--------------------------------------------------------------------------------
/pymake/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/pymake/034ae9ea5b726e03647d049147c5dbf688e94aaf/pymake/__init__.py
--------------------------------------------------------------------------------
/pymake/builtins.py:
--------------------------------------------------------------------------------
1 | # Basic commands implemented in Python
2 | import errno, sys, os, shutil, time
3 | from getopt import getopt, GetoptError
4 |
5 | from pymake import errors
6 |
7 | __all__ = ["mkdir", "rm", "sleep", "touch"]
8 |
9 | def mkdir(args):
10 | """
11 | Emulate some of the behavior of mkdir(1).
12 | Only supports the -p (--parents) argument.
13 | """
14 | try:
15 | opts, args = getopt(args, "p", ["parents"])
16 | except GetoptError as e:
17 | raise errors.PythonError("mkdir: %s" % e, 1)
18 | parents = False
19 | for o, a in opts:
20 | if o in ('-p', '--parents'):
21 | parents = True
22 | for f in args:
23 | try:
24 | if parents:
25 | os.makedirs(f)
26 | else:
27 | os.mkdir(f)
28 | except OSError as e:
29 | if e.errno == errno.EEXIST and parents:
30 | pass
31 | else:
32 | raise errors.PythonError("mkdir: %s" % e, 1)
33 |
34 | def rm(args):
35 | """
36 | Emulate most of the behavior of rm(1).
37 | Only supports the -r (--recursive) and -f (--force) arguments.
38 | """
39 | try:
40 | opts, args = getopt(args, "rRf", ["force", "recursive"])
41 | except GetoptError as e:
42 | raise errors.PythonError("rm: %s" % e, 1)
43 | force = False
44 | recursive = False
45 | for o, a in opts:
46 | if o in ('-f', '--force'):
47 | force = True
48 | elif o in ('-r', '-R', '--recursive'):
49 | recursive = True
50 | for f in args:
51 | if os.path.isdir(f):
52 | if not recursive:
53 | raise errors.PythonError("rm: cannot remove '%s': Is a directory" % f, 1)
54 | else:
55 | shutil.rmtree(f, force)
56 | elif os.path.exists(f):
57 | try:
58 | os.unlink(f)
59 | except:
60 | if not force:
61 | raise errors.PythonError("rm: failed to remove '%s': %s" % (f, sys.exc_info()[0]), 1)
62 | elif not force:
63 | raise errors.PythonError("rm: cannot remove '%s': No such file or directory" % f, 1)
64 |
65 | def sleep(args):
66 | """
67 | Emulate the behavior of sleep(1).
68 | """
69 | total = 0
70 | values = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
71 | for a in args:
72 | multiplier = 1
73 | for k, v in values.items():
74 | if a.endswith(k):
75 | a = a[:-1]
76 | multiplier = v
77 | break
78 | try:
79 | f = float(a)
80 | total += f * multiplier
81 | except ValueError:
82 | raise errors.PythonError("sleep: invalid time interval '%s'" % a, 1)
83 | time.sleep(total)
84 |
85 | def touch(args):
86 | """
87 | Emulate the behavior of touch(1).
88 | """
89 | try:
90 | opts, args = getopt(args, "t:")
91 | except GetoptError as e:
92 | raise errors.PythonError("touch: %s" % e, 1)
93 | opts = dict(opts)
94 | times = None
95 | if '-t' in opts:
96 | import re
97 | from time import mktime, localtime
98 | m = re.match('^(?P(?:\d\d)?\d\d)?(?P\d\d)(?P\d\d)(?P\d\d)(?P\d\d)(?:\.(?P\d\d))?$', opts['-t'])
99 | if not m:
100 | raise errors.PythonError("touch: invalid date format '%s'" % opts['-t'], 1)
101 | def normalized_field(m, f):
102 | if f == 'Y':
103 | if m.group(f) is None:
104 | return localtime()[0]
105 | y = int(m.group(f))
106 | if y < 69:
107 | y += 2000
108 | elif y < 100:
109 | y += 1900
110 | return y
111 | if m.group(f) is None:
112 | return localtime()[0] if f == 'Y' else 0
113 | return int(m.group(f))
114 | time = [normalized_field(m, f) for f in ['Y', 'M', 'D', 'h', 'm', 's']] + [0, 0, -1]
115 | time = mktime(time)
116 | times = (time, time)
117 | for f in args:
118 | if not os.path.exists(f):
119 | open(f, 'a').close()
120 | os.utime(f, times)
121 |
--------------------------------------------------------------------------------
/pymake/command.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 | """
5 | Makefile execution.
6 |
7 | Multiple `makes` can be run within the same process. Each one has an entirely data.Makefile and .Target
8 | structure, environment, and working directory. Typically they will all share a parallel execution context,
9 | except when a submake specifies -j1 when the parent make is building in parallel.
10 | """
11 |
12 | import os, subprocess, sys, logging, time, traceback, re
13 | from optparse import OptionParser
14 | import data, parserdata, process, util
15 | from pymake import errors
16 |
17 | # TODO: If this ever goes from relocatable package to system-installed, this may need to be
18 | # a configured-in path.
19 |
20 | makepypath = util.normaljoin(os.path.dirname(__file__), '../make.py')
21 |
22 | _simpleopts = re.compile(r'^[a-zA-Z]+(\s|$)')
23 | def parsemakeflags(env):
24 | """
25 | Parse MAKEFLAGS from the environment into a sequence of command-line arguments.
26 | """
27 |
28 | makeflags = env.get('MAKEFLAGS', '')
29 | makeflags = makeflags.strip()
30 |
31 | if makeflags == '':
32 | return []
33 |
34 | if _simpleopts.match(makeflags):
35 | makeflags = '-' + makeflags
36 |
37 | opts = []
38 | curopt = ''
39 |
40 | i = 0
41 | while i < len(makeflags):
42 | c = makeflags[i]
43 | if c.isspace():
44 | opts.append(curopt)
45 | curopt = ''
46 | i += 1
47 | while i < len(makeflags) and makeflags[i].isspace():
48 | i += 1
49 | continue
50 |
51 | if c == '\\':
52 | i += 1
53 | if i == len(makeflags):
54 | raise errors.DataError("MAKEFLAGS has trailing backslash")
55 | c = makeflags[i]
56 |
57 | curopt += c
58 | i += 1
59 |
60 | if curopt != '':
61 | opts.append(curopt)
62 |
63 | return opts
64 |
65 | def _version(*args):
66 | print("""pymake: GNU-compatible make program
67 | Copyright (C) 2009 The Mozilla Foundation
68 | This is free software; see the source for copying conditions.
69 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
70 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
71 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
72 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
73 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
74 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
75 | DEALINGS IN THE SOFTWARE.""")
76 |
77 | _log = logging.getLogger('pymake.execution')
78 |
79 | class _MakeContext(object):
80 | def __init__(self, makeflags, makelevel, workdir, context, env, targets, options, ostmts, overrides, cb):
81 | self.makeflags = makeflags
82 | self.makelevel = makelevel
83 |
84 | self.workdir = workdir
85 | self.context = context
86 | self.env = env
87 | self.targets = targets
88 | self.options = options
89 | self.ostmts = ostmts
90 | self.overrides = overrides
91 | self.cb = cb
92 |
93 | self.restarts = 0
94 |
95 | self.remakecb(True)
96 |
97 | def remakecb(self, remade, error=None):
98 | if error is not None:
99 | print(error)
100 | self.context.defer(self.cb, 2)
101 | return
102 |
103 | if remade:
104 | if self.restarts > 0:
105 | _log.info("make.py[%i]: Restarting makefile parsing", self.makelevel)
106 |
107 | self.makefile = data.Makefile(restarts=self.restarts,
108 | make='%s %s' % (sys.executable.replace('\\', '/'), makepypath.replace('\\', '/')),
109 | makeflags=self.makeflags,
110 | makeoverrides=self.overrides,
111 | workdir=self.workdir,
112 | context=self.context,
113 | env=self.env,
114 | makelevel=self.makelevel,
115 | targets=self.targets,
116 | keepgoing=self.options.keepgoing,
117 | silent=self.options.silent,
118 | justprint=self.options.justprint)
119 |
120 | self.restarts += 1
121 |
122 | try:
123 | self.ostmts.execute(self.makefile)
124 | for f in self.options.makefiles:
125 | self.makefile.include(f)
126 | self.makefile.finishparsing()
127 | self.makefile.remakemakefiles(self.remakecb)
128 | except errors.MakeError as e:
129 | print(e)
130 | self.context.defer(self.cb, 2)
131 |
132 | return
133 |
134 | if len(self.targets) == 0:
135 | if self.makefile.defaulttarget is None:
136 | print("No target specified and no default target found.")
137 | self.context.defer(self.cb, 2)
138 | return
139 |
140 | _log.info("Making default target %s", self.makefile.defaulttarget)
141 | self.realtargets = [self.makefile.defaulttarget]
142 | self.tstack = ['']
143 | else:
144 | self.realtargets = self.targets
145 | self.tstack = ['']
146 |
147 | self.makefile.gettarget(self.realtargets.pop(0)).make(self.makefile, self.tstack, cb=self.makecb)
148 |
149 | def makecb(self, error, didanything):
150 | assert error in (True, False)
151 |
152 | if error:
153 | self.context.defer(self.cb, 2)
154 | return
155 |
156 | if not len(self.realtargets):
157 | if self.options.printdir:
158 | print("make.py[%i]: Leaving directory '%s'" % (self.makelevel, self.workdir))
159 | sys.stdout.flush()
160 |
161 | self.context.defer(self.cb, 0)
162 | else:
163 | self.makefile.gettarget(self.realtargets.pop(0)).make(self.makefile, self.tstack, self.makecb)
164 |
165 | def main(args, env, cwd, cb):
166 | """
167 | Start a single makefile execution, given a command line, working directory, and environment.
168 |
169 | @param cb a callback to notify with an exit code when make execution is finished.
170 | """
171 |
172 | try:
173 | makelevel = int(env.get('MAKELEVEL', '0'))
174 |
175 | op = OptionParser()
176 | op.add_option('-f', '--file', '--makefile',
177 | action='append',
178 | dest='makefiles',
179 | default=[])
180 | op.add_option('-d',
181 | action="store_true",
182 | dest="verbose", default=False)
183 | op.add_option('-k', '--keep-going',
184 | action="store_true",
185 | dest="keepgoing", default=False)
186 | op.add_option('--debug-log',
187 | dest="debuglog", default=None)
188 | op.add_option('-C', '--directory',
189 | dest="directory", default=None)
190 | op.add_option('-v', '--version', action="store_true",
191 | dest="printversion", default=False)
192 | op.add_option('-j', '--jobs', type="int",
193 | dest="jobcount", default=1)
194 | op.add_option('-w', '--print-directory', action="store_true",
195 | dest="printdir")
196 | op.add_option('--no-print-directory', action="store_false",
197 | dest="printdir", default=True)
198 | op.add_option('-s', '--silent', action="store_true",
199 | dest="silent", default=False)
200 | op.add_option('-n', '--just-print', '--dry-run', '--recon',
201 | action="store_true",
202 | dest="justprint", default=False)
203 |
204 | options, arguments1 = op.parse_args(parsemakeflags(env))
205 | options, arguments2 = op.parse_args(args, values=options)
206 |
207 | op.destroy()
208 |
209 | arguments = arguments1 + arguments2
210 |
211 | if options.printversion:
212 | _version()
213 | cb(0)
214 | return
215 |
216 | shortflags = []
217 | longflags = []
218 |
219 | if options.keepgoing:
220 | shortflags.append('k')
221 |
222 | if options.printdir:
223 | shortflags.append('w')
224 |
225 | if options.silent:
226 | shortflags.append('s')
227 | options.printdir = False
228 |
229 | if options.justprint:
230 | shortflags.append('n')
231 |
232 | loglevel = logging.WARNING
233 | if options.verbose:
234 | loglevel = logging.DEBUG
235 | shortflags.append('d')
236 |
237 | logkwargs = {}
238 | if options.debuglog:
239 | logkwargs['filename'] = options.debuglog
240 | longflags.append('--debug-log=%s' % options.debuglog)
241 |
242 | if options.directory is None:
243 | workdir = cwd
244 | else:
245 | workdir = util.normaljoin(cwd, options.directory)
246 |
247 | if options.jobcount != 1:
248 | longflags.append('-j%i' % (options.jobcount,))
249 |
250 | makeflags = ''.join(shortflags)
251 | if len(longflags):
252 | makeflags += ' ' + ' '.join(longflags)
253 |
254 | logging.basicConfig(level=loglevel, **logkwargs)
255 |
256 | context = process.getcontext(options.jobcount)
257 |
258 | if options.printdir:
259 | print("make.py[%i]: Entering directory '%s'" % (makelevel, workdir))
260 | sys.stdout.flush()
261 |
262 | if len(options.makefiles) == 0:
263 | if os.path.exists(util.normaljoin(workdir, 'Makefile')):
264 | options.makefiles.append('Makefile')
265 | else:
266 | print("No makefile found")
267 | cb(2)
268 | return
269 |
270 | ostmts, targets, overrides = parserdata.parsecommandlineargs(arguments)
271 |
272 | _MakeContext(makeflags, makelevel, workdir, context, env, targets, options, ostmts, overrides, cb)
273 | except errors.MakeError as e:
274 | print(e)
275 | if options.printdir:
276 | print("make.py[%i]: Leaving directory '%s'" % (makelevel, workdir))
277 | sys.stdout.flush()
278 | cb(2)
279 | return
280 |
--------------------------------------------------------------------------------
/pymake/errors.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | class MakeError(Exception):
4 | def __init__(self, message, loc=None):
5 | self.msg = message
6 | self.loc = loc
7 |
8 | def __str__(self):
9 | locstr = ''
10 | if self.loc is not None:
11 | locstr = "%s:" % (self.loc,)
12 |
13 | return "%s%s" % (locstr, self.msg)
14 |
15 |
16 | class SyntaxError(MakeError):
17 | pass
18 |
19 |
20 | class DataError(MakeError):
21 | pass
22 |
23 |
24 | class ResolutionError(DataError):
25 | """
26 | Raised when dependency resolution fails, either due to recursion or to missing
27 | prerequisites.This is separately catchable so that implicit rule search can try things
28 | without having to commit.
29 | """
30 | pass
31 |
32 |
33 | class PythonError(Exception):
34 | def __init__(self, message, exitcode):
35 | Exception.__init__(self)
36 | self.message = message
37 | self.exitcode = exitcode
38 |
39 | def __str__(self):
40 | return self.message
41 |
42 |
43 |
--------------------------------------------------------------------------------
/pymake/functions.py:
--------------------------------------------------------------------------------
1 | """
2 | Makefile functions.
3 | """
4 | from __future__ import print_function
5 |
6 | import parser, util
7 | import subprocess, os, logging, sys
8 | from globrelative import glob
9 | from pymake import errors
10 |
11 | log = logging.getLogger('pymake.data')
12 |
13 | def emit_expansions(descend, *expansions):
14 | """Helper function to emit all expansions within an input set."""
15 | for expansion in expansions:
16 | yield expansion
17 |
18 | if not descend or not isinstance(expansion, list):
19 | continue
20 |
21 | for e, is_func in expansion:
22 | if is_func:
23 | for exp in e.expansions(True):
24 | yield exp
25 | else:
26 | yield e
27 |
28 | class Function(object):
29 | """
30 | An object that represents a function call. This class is always subclassed
31 | with the following methods and attributes:
32 |
33 | minargs = minimum # of arguments
34 | maxargs = maximum # of arguments (0 means unlimited)
35 |
36 | def resolve(self, makefile, variables, fd, setting)
37 | Calls the function
38 | calls fd.write() with strings
39 | """
40 |
41 | __slots__ = ('_arguments', 'loc')
42 |
43 | def __init__(self, loc):
44 | self._arguments = []
45 | self.loc = loc
46 | assert self.minargs > 0
47 |
48 | def __getitem__(self, key):
49 | return self._arguments[key]
50 |
51 | def setup(self):
52 | argc = len(self._arguments)
53 |
54 | if argc < self.minargs:
55 | raise errors.DataError("Not enough arguments to function %s, requires %s" % (self.name, self.minargs), self.loc)
56 |
57 | assert self.maxargs == 0 or argc <= self.maxargs, "Parser screwed up, gave us too many args"
58 |
59 | def append(self, arg):
60 | assert isinstance(arg, (data.Expansion, data.StringExpansion))
61 | self._arguments.append(arg)
62 |
63 | def to_source(self):
64 | """Convert the function back to make file "source" code."""
65 | if not hasattr(self, 'name'):
66 | raise Exception("%s must implement to_source()." % self.__class__)
67 |
68 | # The default implementation simply prints the function name and all
69 | # the arguments joined by a comma.
70 | # According to the GNU make manual Section 8.1, whitespace around
71 | # arguments is *not* part of the argument's value. So, we trim excess
72 | # white space so we have consistent behavior.
73 | args = []
74 | curly = False
75 | for i, arg in enumerate(self._arguments):
76 | arg = arg.to_source()
77 |
78 | if i == 0:
79 | arg = arg.lstrip()
80 |
81 | # Are balanced parens even OK?
82 | if arg.count('(') != arg.count(')'):
83 | curly = True
84 |
85 | args.append(arg)
86 |
87 | if curly:
88 | return '${%s %s}' % (self.name, ','.join(args))
89 |
90 | return '$(%s %s)' % (self.name, ','.join(args))
91 |
92 | def expansions(self, descend=False):
93 | """Obtain all expansions contained within this function.
94 |
95 | By default, only expansions directly part of this function are
96 | returned. If descend is True, we will descend into child expansions and
97 | return all of the composite parts.
98 |
99 | This is a generator for pymake.data.BaseExpansion instances.
100 | """
101 | # Our default implementation simply returns arguments. More advanced
102 | # functions like variable references may need their own implementation.
103 | return emit_expansions(descend, *self._arguments)
104 |
105 | @property
106 | def is_filesystem_dependent(self):
107 | """Exposes whether this function depends on the filesystem for results.
108 |
109 | If True, the function touches the filesystem as part of evaluation.
110 |
111 | This only tests whether the function itself uses the filesystem. If
112 | this function has arguments that are functions that touch the
113 | filesystem, this will return False.
114 | """
115 | return False
116 |
117 | def __len__(self):
118 | return len(self._arguments)
119 |
120 | def __repr__(self):
121 | return "%s<%s>(%r)" % (
122 | self.__class__.__name__, self.loc,
123 | ','.join([repr(a) for a in self._arguments]),
124 | )
125 |
126 | def __eq__(self, other):
127 | if not hasattr(self, 'name'):
128 | raise Exception("%s must implement __eq__." % self.__class__)
129 |
130 | if type(self) != type(other):
131 | return False
132 |
133 | if self.name != other.name:
134 | return False
135 |
136 | if len(self._arguments) != len(other._arguments):
137 | return False
138 |
139 | for i in range(len(self._arguments)):
140 | # According to the GNU make manual Section 8.1, whitespace around
141 | # arguments is *not* part of the argument's value. So, we do a
142 | # whitespace-agnostic comparison.
143 | if i == 0:
144 | a = self._arguments[i]
145 | a.lstrip()
146 |
147 | b = other._arguments[i]
148 | b.lstrip()
149 |
150 | if a != b:
151 | return False
152 |
153 | continue
154 |
155 | if self._arguments[i] != other._arguments[i]:
156 | return False
157 |
158 | return True
159 |
160 | def __ne__(self, other):
161 | return not self.__eq__(other)
162 |
163 | class VariableRef(Function):
164 | AUTOMATIC_VARIABLES = set(['@', '%', '<', '?', '^', '+', '|', '*'])
165 |
166 | __slots__ = ('vname', 'loc')
167 |
168 | def __init__(self, loc, vname):
169 | self.loc = loc
170 | assert isinstance(vname, (data.Expansion, data.StringExpansion))
171 | self.vname = vname
172 |
173 | def setup(self):
174 | assert False, "Shouldn't get here"
175 |
176 | def resolve(self, makefile, variables, fd, setting):
177 | vname = self.vname.resolvestr(makefile, variables, setting)
178 | if vname in setting:
179 | raise errors.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc)
180 |
181 | flavor, source, value = variables.get(vname)
182 | if value is None:
183 | log.debug("%s: variable '%s' was not set" % (self.loc, vname))
184 | return
185 |
186 | value.resolve(makefile, variables, fd, setting + [vname])
187 |
188 | def to_source(self):
189 | if isinstance(self.vname, data.StringExpansion):
190 | if self.vname.s in self.AUTOMATIC_VARIABLES:
191 | return '$%s' % self.vname.s
192 |
193 | return '$(%s)' % self.vname.s
194 |
195 | return '$(%s)' % self.vname.to_source()
196 |
197 | def expansions(self, descend=False):
198 | return emit_expansions(descend, self.vname)
199 |
200 | def __repr__(self):
201 | return "VariableRef<%s>(%r)" % (self.loc, self.vname)
202 |
203 | def __eq__(self, other):
204 | if not isinstance(other, VariableRef):
205 | return False
206 |
207 | return self.vname == other.vname
208 |
209 | class SubstitutionRef(Function):
210 | """$(VARNAME:.c=.o) and $(VARNAME:%.c=%.o)"""
211 |
212 | __slots__ = ('loc', 'vname', 'substfrom', 'substto')
213 |
214 | def __init__(self, loc, varname, substfrom, substto):
215 | self.loc = loc
216 | self.vname = varname
217 | self.substfrom = substfrom
218 | self.substto = substto
219 |
220 | def setup(self):
221 | assert False, "Shouldn't get here"
222 |
223 | def resolve(self, makefile, variables, fd, setting):
224 | vname = self.vname.resolvestr(makefile, variables, setting)
225 | if vname in setting:
226 | raise errors.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc)
227 |
228 | substfrom = self.substfrom.resolvestr(makefile, variables, setting)
229 | substto = self.substto.resolvestr(makefile, variables, setting)
230 |
231 | flavor, source, value = variables.get(vname)
232 | if value is None:
233 | log.debug("%s: variable '%s' was not set" % (self.loc, vname))
234 | return
235 |
236 | f = data.Pattern(substfrom)
237 | if not f.ispattern():
238 | f = data.Pattern('%' + substfrom)
239 | substto = '%' + substto
240 |
241 | fd.write(' '.join([f.subst(substto, word, False)
242 | for word in value.resolvesplit(makefile, variables, setting + [vname])]))
243 |
244 | def to_source(self):
245 | return '$(%s:%s=%s)' % (
246 | self.vname.to_source(),
247 | self.substfrom.to_source(),
248 | self.substto.to_source())
249 |
250 | def expansions(self, descend=False):
251 | return emit_expansions(descend, self.vname, self.substfrom,
252 | self.substto)
253 |
254 | def __repr__(self):
255 | return "SubstitutionRef<%s>(%r:%r=%r)" % (
256 | self.loc, self.vname, self.substfrom, self.substto,)
257 |
258 | def __eq__(self, other):
259 | if not isinstance(other, SubstitutionRef):
260 | return False
261 |
262 | return self.vname == other.vname and self.substfrom == other.substfrom \
263 | and self.substto == other.substto
264 |
265 | class SubstFunction(Function):
266 | name = 'subst'
267 | minargs = 3
268 | maxargs = 3
269 |
270 | __slots__ = Function.__slots__
271 |
272 | def resolve(self, makefile, variables, fd, setting):
273 | s = self._arguments[0].resolvestr(makefile, variables, setting)
274 | r = self._arguments[1].resolvestr(makefile, variables, setting)
275 | d = self._arguments[2].resolvestr(makefile, variables, setting)
276 | fd.write(d.replace(s, r))
277 |
278 | class PatSubstFunction(Function):
279 | name = 'patsubst'
280 | minargs = 3
281 | maxargs = 3
282 |
283 | __slots__ = Function.__slots__
284 |
285 | def resolve(self, makefile, variables, fd, setting):
286 | s = self._arguments[0].resolvestr(makefile, variables, setting)
287 | r = self._arguments[1].resolvestr(makefile, variables, setting)
288 |
289 | p = data.Pattern(s)
290 | fd.write(' '.join([p.subst(r, word, False)
291 | for word in self._arguments[2].resolvesplit(makefile, variables, setting)]))
292 |
293 | class StripFunction(Function):
294 | name = 'strip'
295 | minargs = 1
296 | maxargs = 1
297 |
298 | __slots__ = Function.__slots__
299 |
300 | def resolve(self, makefile, variables, fd, setting):
301 | util.joiniter(fd, self._arguments[0].resolvesplit(makefile, variables, setting))
302 |
303 | class FindstringFunction(Function):
304 | name = 'findstring'
305 | minargs = 2
306 | maxargs = 2
307 |
308 | __slots__ = Function.__slots__
309 |
310 | def resolve(self, makefile, variables, fd, setting):
311 | s = self._arguments[0].resolvestr(makefile, variables, setting)
312 | r = self._arguments[1].resolvestr(makefile, variables, setting)
313 | if r.find(s) == -1:
314 | return
315 | fd.write(s)
316 |
317 | class FilterFunction(Function):
318 | name = 'filter'
319 | minargs = 2
320 | maxargs = 2
321 |
322 | __slots__ = Function.__slots__
323 |
324 | def resolve(self, makefile, variables, fd, setting):
325 | plist = [data.Pattern(p)
326 | for p in self._arguments[0].resolvesplit(makefile, variables, setting)]
327 |
328 | fd.write(' '.join([w for w in self._arguments[1].resolvesplit(makefile, variables, setting)
329 | if util.any((p.match(w) for p in plist))]))
330 |
331 | class FilteroutFunction(Function):
332 | name = 'filter-out'
333 | minargs = 2
334 | maxargs = 2
335 |
336 | __slots__ = Function.__slots__
337 |
338 | def resolve(self, makefile, variables, fd, setting):
339 | plist = [data.Pattern(p)
340 | for p in self._arguments[0].resolvesplit(makefile, variables, setting)]
341 |
342 | fd.write(' '.join([w for w in self._arguments[1].resolvesplit(makefile, variables, setting)
343 | if not util.any((p.match(w) for p in plist))]))
344 |
345 | class SortFunction(Function):
346 | name = 'sort'
347 | minargs = 1
348 | maxargs = 1
349 |
350 | __slots__ = Function.__slots__
351 |
352 | def resolve(self, makefile, variables, fd, setting):
353 | d = set(self._arguments[0].resolvesplit(makefile, variables, setting))
354 | util.joiniter(fd, sorted(d))
355 |
356 | class WordFunction(Function):
357 | name = 'word'
358 | minargs = 2
359 | maxargs = 2
360 |
361 | __slots__ = Function.__slots__
362 |
363 | def resolve(self, makefile, variables, fd, setting):
364 | n = self._arguments[0].resolvestr(makefile, variables, setting)
365 | # TODO: provide better error if this doesn't convert
366 | n = int(n)
367 | words = list(self._arguments[1].resolvesplit(makefile, variables, setting))
368 | if n < 1 or n > len(words):
369 | return
370 | fd.write(words[n - 1])
371 |
372 | class WordlistFunction(Function):
373 | name = 'wordlist'
374 | minargs = 3
375 | maxargs = 3
376 |
377 | __slots__ = Function.__slots__
378 |
379 | def resolve(self, makefile, variables, fd, setting):
380 | nfrom = self._arguments[0].resolvestr(makefile, variables, setting)
381 | nto = self._arguments[1].resolvestr(makefile, variables, setting)
382 | # TODO: provide better errors if this doesn't convert
383 | nfrom = int(nfrom)
384 | nto = int(nto)
385 |
386 | words = list(self._arguments[2].resolvesplit(makefile, variables, setting))
387 |
388 | if nfrom < 1:
389 | nfrom = 1
390 | if nto < 1:
391 | nto = 1
392 |
393 | util.joiniter(fd, words[nfrom - 1:nto])
394 |
395 | class WordsFunction(Function):
396 | name = 'words'
397 | minargs = 1
398 | maxargs = 1
399 |
400 | __slots__ = Function.__slots__
401 |
402 | def resolve(self, makefile, variables, fd, setting):
403 | fd.write(str(len(self._arguments[0].resolvesplit(makefile, variables, setting))))
404 |
405 | class FirstWordFunction(Function):
406 | name = 'firstword'
407 | minargs = 1
408 | maxargs = 1
409 |
410 | __slots__ = Function.__slots__
411 |
412 | def resolve(self, makefile, variables, fd, setting):
413 | l = self._arguments[0].resolvesplit(makefile, variables, setting)
414 | if len(l):
415 | fd.write(l[0])
416 |
417 | class LastWordFunction(Function):
418 | name = 'lastword'
419 | minargs = 1
420 | maxargs = 1
421 |
422 | __slots__ = Function.__slots__
423 |
424 | def resolve(self, makefile, variables, fd, setting):
425 | l = self._arguments[0].resolvesplit(makefile, variables, setting)
426 | if len(l):
427 | fd.write(l[-1])
428 |
429 | def pathsplit(path, default='./'):
430 | """
431 | Splits a path into dirpart, filepart on the last slash. If there is no slash, dirpart
432 | is ./
433 | """
434 | dir, slash, file = util.strrpartition(path, '/')
435 | if dir == '':
436 | return default, file
437 |
438 | return dir + slash, file
439 |
440 | class DirFunction(Function):
441 | name = 'dir'
442 | minargs = 1
443 | maxargs = 1
444 |
445 | def resolve(self, makefile, variables, fd, setting):
446 | fd.write(' '.join([pathsplit(path)[0]
447 | for path in self._arguments[0].resolvesplit(makefile, variables, setting)]))
448 |
449 | class NotDirFunction(Function):
450 | name = 'notdir'
451 | minargs = 1
452 | maxargs = 1
453 |
454 | __slots__ = Function.__slots__
455 |
456 | def resolve(self, makefile, variables, fd, setting):
457 | fd.write(' '.join([pathsplit(path)[1]
458 | for path in self._arguments[0].resolvesplit(makefile, variables, setting)]))
459 |
460 | class SuffixFunction(Function):
461 | name = 'suffix'
462 | minargs = 1
463 | maxargs = 1
464 |
465 | __slots__ = Function.__slots__
466 |
467 | @staticmethod
468 | def suffixes(words):
469 | for w in words:
470 | dir, file = pathsplit(w)
471 | base, dot, suffix = util.strrpartition(file, '.')
472 | if base != '':
473 | yield dot + suffix
474 |
475 | def resolve(self, makefile, variables, fd, setting):
476 | util.joiniter(fd, self.suffixes(self._arguments[0].resolvesplit(makefile, variables, setting)))
477 |
478 | class BasenameFunction(Function):
479 | name = 'basename'
480 | minargs = 1
481 | maxargs = 1
482 |
483 | __slots__ = Function.__slots__
484 |
485 | @staticmethod
486 | def basenames(words):
487 | for w in words:
488 | dir, file = pathsplit(w, '')
489 | base, dot, suffix = util.strrpartition(file, '.')
490 | if dot == '':
491 | base = suffix
492 |
493 | yield dir + base
494 |
495 | def resolve(self, makefile, variables, fd, setting):
496 | util.joiniter(fd, self.basenames(self._arguments[0].resolvesplit(makefile, variables, setting)))
497 |
498 | class AddSuffixFunction(Function):
499 | name = 'addsuffix'
500 | minargs = 2
501 | maxargs = 2
502 |
503 | __slots__ = Function.__slots__
504 |
505 | def resolve(self, makefile, variables, fd, setting):
506 | suffix = self._arguments[0].resolvestr(makefile, variables, setting)
507 |
508 | fd.write(' '.join([w + suffix for w in self._arguments[1].resolvesplit(makefile, variables, setting)]))
509 |
510 | class AddPrefixFunction(Function):
511 | name = 'addprefix'
512 | minargs = 2
513 | maxargs = 2
514 |
515 | def resolve(self, makefile, variables, fd, setting):
516 | prefix = self._arguments[0].resolvestr(makefile, variables, setting)
517 |
518 | fd.write(' '.join([prefix + w for w in self._arguments[1].resolvesplit(makefile, variables, setting)]))
519 |
520 | class JoinFunction(Function):
521 | name = 'join'
522 | minargs = 2
523 | maxargs = 2
524 |
525 | __slots__ = Function.__slots__
526 |
527 | @staticmethod
528 | def iterjoin(l1, l2):
529 | for i in range(0, max(len(l1), len(l2))):
530 | i1 = i < len(l1) and l1[i] or ''
531 | i2 = i < len(l2) and l2[i] or ''
532 | yield i1 + i2
533 |
534 | def resolve(self, makefile, variables, fd, setting):
535 | list1 = list(self._arguments[0].resolvesplit(makefile, variables, setting))
536 | list2 = list(self._arguments[1].resolvesplit(makefile, variables, setting))
537 |
538 | util.joiniter(fd, self.iterjoin(list1, list2))
539 |
540 | class WildcardFunction(Function):
541 | name = 'wildcard'
542 | minargs = 1
543 | maxargs = 1
544 |
545 | __slots__ = Function.__slots__
546 |
547 | def resolve(self, makefile, variables, fd, setting):
548 | patterns = self._arguments[0].resolvesplit(makefile, variables, setting)
549 |
550 | fd.write(' '.join([x.replace('\\','/')
551 | for p in patterns
552 | for x in glob(makefile.workdir, p)]))
553 |
554 | @property
555 | def is_filesystem_dependent(self):
556 | return True
557 |
558 | class RealpathFunction(Function):
559 | name = 'realpath'
560 | minargs = 1
561 | maxargs = 1
562 |
563 | def resolve(self, makefile, variables, fd, setting):
564 | fd.write(' '.join([os.path.realpath(os.path.join(makefile.workdir, path)).replace('\\', '/')
565 | for path in self._arguments[0].resolvesplit(makefile, variables, setting)]))
566 |
567 | def is_filesystem_dependent(self):
568 | return True
569 |
570 | class AbspathFunction(Function):
571 | name = 'abspath'
572 | minargs = 1
573 | maxargs = 1
574 |
575 | __slots__ = Function.__slots__
576 |
577 | def resolve(self, makefile, variables, fd, setting):
578 | assert os.path.isabs(makefile.workdir)
579 | fd.write(' '.join([util.normaljoin(makefile.workdir, path).replace('\\', '/')
580 | for path in self._arguments[0].resolvesplit(makefile, variables, setting)]))
581 |
582 | class IfFunction(Function):
583 | name = 'if'
584 | minargs = 1
585 | maxargs = 3
586 |
587 | __slots__ = Function.__slots__
588 |
589 | def setup(self):
590 | Function.setup(self)
591 | self._arguments[0].lstrip()
592 | self._arguments[0].rstrip()
593 |
594 | def resolve(self, makefile, variables, fd, setting):
595 | condition = self._arguments[0].resolvestr(makefile, variables, setting)
596 |
597 | if len(condition):
598 | self._arguments[1].resolve(makefile, variables, fd, setting)
599 | elif len(self._arguments) > 2:
600 | return self._arguments[2].resolve(makefile, variables, fd, setting)
601 |
602 | class OrFunction(Function):
603 | name = 'or'
604 | minargs = 1
605 | maxargs = 0
606 |
607 | __slots__ = Function.__slots__
608 |
609 | def resolve(self, makefile, variables, fd, setting):
610 | for arg in self._arguments:
611 | r = arg.resolvestr(makefile, variables, setting)
612 | if r != '':
613 | fd.write(r)
614 | return
615 |
616 | class AndFunction(Function):
617 | name = 'and'
618 | minargs = 1
619 | maxargs = 0
620 |
621 | __slots__ = Function.__slots__
622 |
623 | def resolve(self, makefile, variables, fd, setting):
624 | r = ''
625 |
626 | for arg in self._arguments:
627 | r = arg.resolvestr(makefile, variables, setting)
628 | if r == '':
629 | return
630 |
631 | fd.write(r)
632 |
633 | class ForEachFunction(Function):
634 | name = 'foreach'
635 | minargs = 3
636 | maxargs = 3
637 |
638 | __slots__ = Function.__slots__
639 |
640 | def resolve(self, makefile, variables, fd, setting):
641 | vname = self._arguments[0].resolvestr(makefile, variables, setting)
642 | e = self._arguments[2]
643 |
644 | v = data.Variables(parent=variables)
645 | firstword = True
646 |
647 | for w in self._arguments[1].resolvesplit(makefile, variables, setting):
648 | if firstword:
649 | firstword = False
650 | else:
651 | fd.write(' ')
652 |
653 | # The $(origin) of the local variable must be "automatic" to
654 | # conform with GNU make. However, automatic variables have low
655 | # priority. So, we must force its assignment to occur.
656 | v.set(vname, data.Variables.FLAVOR_SIMPLE,
657 | data.Variables.SOURCE_AUTOMATIC, w, force=True)
658 | e.resolve(makefile, v, fd, setting)
659 |
660 | class CallFunction(Function):
661 | name = 'call'
662 | minargs = 1
663 | maxargs = 0
664 |
665 | __slots__ = Function.__slots__
666 |
667 | def resolve(self, makefile, variables, fd, setting):
668 | vname = self._arguments[0].resolvestr(makefile, variables, setting)
669 | if vname in setting:
670 | raise errors.DataError("Recursively setting variable '%s'" % (vname,))
671 |
672 | v = data.Variables(parent=variables)
673 | v.set('0', data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, vname)
674 | for i in range(1, len(self._arguments)):
675 | param = self._arguments[i].resolvestr(makefile, variables, setting)
676 | v.set(str(i), data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, param)
677 |
678 | flavor, source, e = variables.get(vname)
679 |
680 | if e is None:
681 | return
682 |
683 | if flavor == data.Variables.FLAVOR_SIMPLE:
684 | log.warning("%s: calling variable '%s' which is simply-expanded" % (self.loc, vname))
685 |
686 | # but we'll do it anyway
687 | e.resolve(makefile, v, fd, setting + [vname])
688 |
689 | class ValueFunction(Function):
690 | name = 'value'
691 | minargs = 1
692 | maxargs = 1
693 |
694 | __slots__ = Function.__slots__
695 |
696 | def resolve(self, makefile, variables, fd, setting):
697 | varname = self._arguments[0].resolvestr(makefile, variables, setting)
698 |
699 | flavor, source, value = variables.get(varname, expand=False)
700 | if value is not None:
701 | fd.write(value)
702 |
703 | class EvalFunction(Function):
704 | name = 'eval'
705 | minargs = 1
706 | maxargs = 1
707 |
708 | def resolve(self, makefile, variables, fd, setting):
709 | if makefile.parsingfinished:
710 | # GNU make allows variables to be set by recursive expansion during
711 | # command execution. This seems really dumb to me, so I don't!
712 | raise errors.DataError("$(eval) not allowed via recursive expansion after parsing is finished", self.loc)
713 |
714 | stmts = parser.parsestring(self._arguments[0].resolvestr(makefile, variables, setting),
715 | 'evaluation from %s' % self.loc)
716 | stmts.execute(makefile)
717 |
718 | class OriginFunction(Function):
719 | name = 'origin'
720 | minargs = 1
721 | maxargs = 1
722 |
723 | __slots__ = Function.__slots__
724 |
725 | def resolve(self, makefile, variables, fd, setting):
726 | vname = self._arguments[0].resolvestr(makefile, variables, setting)
727 |
728 | flavor, source, value = variables.get(vname)
729 | if source is None:
730 | r = 'undefined'
731 | elif source == data.Variables.SOURCE_OVERRIDE:
732 | r = 'override'
733 |
734 | elif source == data.Variables.SOURCE_MAKEFILE:
735 | r = 'file'
736 | elif source == data.Variables.SOURCE_ENVIRONMENT:
737 | r = 'environment'
738 | elif source == data.Variables.SOURCE_COMMANDLINE:
739 | r = 'command line'
740 | elif source == data.Variables.SOURCE_AUTOMATIC:
741 | r = 'automatic'
742 | elif source == data.Variables.SOURCE_IMPLICIT:
743 | r = 'default'
744 |
745 | fd.write(r)
746 |
747 | class FlavorFunction(Function):
748 | name = 'flavor'
749 | minargs = 1
750 | maxargs = 1
751 |
752 | __slots__ = Function.__slots__
753 |
754 | def resolve(self, makefile, variables, fd, setting):
755 | varname = self._arguments[0].resolvestr(makefile, variables, setting)
756 |
757 | flavor, source, value = variables.get(varname)
758 | if flavor is None:
759 | r = 'undefined'
760 | elif flavor == data.Variables.FLAVOR_RECURSIVE:
761 | r = 'recursive'
762 | elif flavor == data.Variables.FLAVOR_SIMPLE:
763 | r = 'simple'
764 | fd.write(r)
765 |
766 | class ShellFunction(Function):
767 | name = 'shell'
768 | minargs = 1
769 | maxargs = 1
770 |
771 | __slots__ = Function.__slots__
772 |
773 | def resolve(self, makefile, variables, fd, setting):
774 | from process import prepare_command
775 | cline = self._arguments[0].resolvestr(makefile, variables, setting)
776 | executable, cline = prepare_command(cline, makefile.workdir, self.loc)
777 |
778 | # subprocess.Popen doesn't use the PATH set in the env argument for
779 | # finding the executable on some platforms (but strangely it does on
780 | # others!), so set os.environ['PATH'] explicitly.
781 | oldpath = os.environ['PATH']
782 | if makefile.env is not None and 'PATH' in makefile.env:
783 | os.environ['PATH'] = makefile.env['PATH']
784 |
785 | log.debug("%s: running command '%s'" % (self.loc, ' '.join(cline)))
786 | try:
787 | p = subprocess.Popen(cline, executable=executable, env=makefile.env, shell=False,
788 | stdout=subprocess.PIPE, cwd=makefile.workdir)
789 | except OSError as e:
790 | print("Error executing command %s" % cline[0], e, file=sys.stderr)
791 | return
792 | finally:
793 | os.environ['PATH'] = oldpath
794 |
795 | stdout, stderr = p.communicate()
796 | stdout = stdout.replace('\r\n', '\n')
797 | if stdout.endswith('\n'):
798 | stdout = stdout[:-1]
799 | stdout = stdout.replace('\n', ' ')
800 |
801 | fd.write(stdout)
802 |
803 | class ErrorFunction(Function):
804 | name = 'error'
805 | minargs = 1
806 | maxargs = 1
807 |
808 | __slots__ = Function.__slots__
809 |
810 | def resolve(self, makefile, variables, fd, setting):
811 | v = self._arguments[0].resolvestr(makefile, variables, setting)
812 | raise errors.DataError(v, self.loc)
813 |
814 | class WarningFunction(Function):
815 | name = 'warning'
816 | minargs = 1
817 | maxargs = 1
818 |
819 | __slots__ = Function.__slots__
820 |
821 | def resolve(self, makefile, variables, fd, setting):
822 | v = self._arguments[0].resolvestr(makefile, variables, setting)
823 | log.warning(v)
824 |
825 | class InfoFunction(Function):
826 | name = 'info'
827 | minargs = 1
828 | maxargs = 1
829 |
830 | __slots__ = Function.__slots__
831 |
832 | def resolve(self, makefile, variables, fd, setting):
833 | v = self._arguments[0].resolvestr(makefile, variables, setting)
834 | print(v)
835 |
836 | functionmap = {
837 | 'subst': SubstFunction,
838 | 'patsubst': PatSubstFunction,
839 | 'strip': StripFunction,
840 | 'findstring': FindstringFunction,
841 | 'filter': FilterFunction,
842 | 'filter-out': FilteroutFunction,
843 | 'sort': SortFunction,
844 | 'word': WordFunction,
845 | 'wordlist': WordlistFunction,
846 | 'words': WordsFunction,
847 | 'firstword': FirstWordFunction,
848 | 'lastword': LastWordFunction,
849 | 'dir': DirFunction,
850 | 'notdir': NotDirFunction,
851 | 'suffix': SuffixFunction,
852 | 'basename': BasenameFunction,
853 | 'addsuffix': AddSuffixFunction,
854 | 'addprefix': AddPrefixFunction,
855 | 'join': JoinFunction,
856 | 'wildcard': WildcardFunction,
857 | 'realpath': RealpathFunction,
858 | 'abspath': AbspathFunction,
859 | 'if': IfFunction,
860 | 'or': OrFunction,
861 | 'and': AndFunction,
862 | 'foreach': ForEachFunction,
863 | 'call': CallFunction,
864 | 'value': ValueFunction,
865 | 'eval': EvalFunction,
866 | 'origin': OriginFunction,
867 | 'flavor': FlavorFunction,
868 | 'shell': ShellFunction,
869 | 'error': ErrorFunction,
870 | 'warning': WarningFunction,
871 | 'info': InfoFunction,
872 | }
873 |
874 | import data
875 |
--------------------------------------------------------------------------------
/pymake/globrelative.py:
--------------------------------------------------------------------------------
1 | """
2 | Filename globbing like the python glob module with minor differences:
3 |
4 | * glob relative to an arbitrary directory
5 | * include . and ..
6 | * check that link targets exist, not just links
7 | """
8 |
9 | import os, re, fnmatch
10 | import util
11 |
12 | _globcheck = re.compile('[[*?]')
13 |
14 | def hasglob(p):
15 | return _globcheck.search(p) is not None
16 |
17 | def glob(fsdir, path):
18 | """
19 | Yield paths matching the path glob. Sorts as a bonus. Excludes '.' and '..'
20 | """
21 |
22 | dir, leaf = os.path.split(path)
23 | if dir == '':
24 | return globpattern(fsdir, leaf)
25 |
26 | if hasglob(dir):
27 | dirsfound = glob(fsdir, dir)
28 | else:
29 | dirsfound = [dir]
30 |
31 | r = []
32 |
33 | for dir in dirsfound:
34 | fspath = util.normaljoin(fsdir, dir)
35 | if not os.path.isdir(fspath):
36 | continue
37 |
38 | r.extend((util.normaljoin(dir, found) for found in globpattern(fspath, leaf)))
39 |
40 | return r
41 |
42 | def globpattern(dir, pattern):
43 | """
44 | Return leaf names in the specified directory which match the pattern.
45 | """
46 |
47 | if not hasglob(pattern):
48 | if pattern == '':
49 | if os.path.isdir(dir):
50 | return ['']
51 | return []
52 |
53 | if os.path.exists(util.normaljoin(dir, pattern)):
54 | return [pattern]
55 | return []
56 |
57 | leaves = os.listdir(dir) + ['.', '..']
58 |
59 | # "hidden" filenames are a bit special
60 | if not pattern.startswith('.'):
61 | leaves = [leaf for leaf in leaves
62 | if not leaf.startswith('.')]
63 |
64 | leaves = fnmatch.filter(leaves, pattern)
65 | leaves = [l for l in leaves if os.path.exists(util.normaljoin(dir, l))]
66 |
67 | leaves.sort()
68 | return leaves
69 |
--------------------------------------------------------------------------------
/pymake/implicit.py:
--------------------------------------------------------------------------------
1 | """
2 | Implicit variables; perhaps in the future this will also include some implicit
3 | rules, at least match-anything cancellation rules.
4 | """
5 |
6 | variables = {
7 | 'MKDIR': '%pymake.builtins mkdir',
8 | 'RM': '%pymake.builtins rm -f',
9 | 'SLEEP': '%pymake.builtins sleep',
10 | 'TOUCH': '%pymake.builtins touch',
11 | '.LIBPATTERNS': 'lib%.so lib%.a',
12 | '.PYMAKE': '1',
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/pymake/process.py:
--------------------------------------------------------------------------------
1 | """
2 | Skipping shell invocations is good, when possible. This wrapper around subprocess does dirty work of
3 | parsing command lines into argv and making sure that no shell magic is being used.
4 | """
5 | from __future__ import print_function
6 |
7 | #TODO: ship pyprocessing?
8 | import multiprocessing
9 | import subprocess, shlex, re, logging, sys, traceback, os, imp, glob
10 | import site
11 | from collections import deque
12 | # XXXkhuey Work around http://bugs.python.org/issue1731717
13 | subprocess._cleanup = lambda: None
14 | import command, util
15 | from pymake import errors
16 | if sys.platform=='win32':
17 | import win32process
18 |
19 | _log = logging.getLogger('pymake.process')
20 |
21 | _escapednewlines = re.compile(r'\\\n')
22 |
23 | def tokens2re(tokens):
24 | # Create a pattern for non-escaped tokens, in the form:
25 | # (?pattern)
30 | # which matches the pattern and captures it in a named match group.
31 | # The group names and patterns come are given as a dict in the function
32 | # argument.
33 | nonescaped = r'(?%s)' % (name, value) for name, value in tokens.items())
34 | # The final pattern matches either the above pattern, or an escaped
35 | # backslash, captured in the "escape" match group.
36 | return re.compile('(?:%s|%s)' % (nonescaped, r'(?P\\\\)'))
37 |
38 | _unquoted_tokens = tokens2re({
39 | 'whitespace': r'[\t\r\n ]+',
40 | 'quote': r'[\'"]',
41 | 'comment': '#',
42 | 'special': r'[<>&|`~(){}$;]',
43 | 'backslashed': r'\\[^\\]',
44 | 'glob': r'[\*\?]',
45 | })
46 |
47 | _doubly_quoted_tokens = tokens2re({
48 | 'quote': '"',
49 | 'backslashedquote': r'\\"',
50 | 'special': '\$',
51 | 'backslashed': r'\\[^\\"]',
52 | })
53 |
54 | class MetaCharacterException(Exception):
55 | def __init__(self, char):
56 | self.char = char
57 |
58 | class ClineSplitter(list):
59 | """
60 | Parses a given command line string and creates a list of command
61 | and arguments, with wildcard expansion.
62 | """
63 | def __init__(self, cline, cwd):
64 | self.cwd = cwd
65 | self.arg = None
66 | self.cline = cline
67 | self.glob = False
68 | self._parse_unquoted()
69 |
70 | def _push(self, str):
71 | """
72 | Push the given string as part of the current argument
73 | """
74 | if self.arg is None:
75 | self.arg = ''
76 | self.arg += str
77 |
78 | def _next(self):
79 | """
80 | Finalize current argument, effectively adding it to the list.
81 | Perform globbing if needed.
82 | """
83 | if self.arg is None:
84 | return
85 | if self.glob:
86 | if os.path.isabs(self.arg):
87 | path = self.arg
88 | else:
89 | path = os.path.join(self.cwd, self.arg)
90 | globbed = glob.glob(path)
91 | if not globbed:
92 | # If globbing doesn't find anything, the literal string is
93 | # used.
94 | self.append(self.arg)
95 | else:
96 | self.extend(f[len(path)-len(self.arg):] for f in globbed)
97 | self.glob = False
98 | else:
99 | self.append(self.arg)
100 | self.arg = None
101 |
102 | def _parse_unquoted(self):
103 | """
104 | Parse command line remainder in the context of an unquoted string.
105 | """
106 | while self.cline:
107 | # Find the next token
108 | m = _unquoted_tokens.search(self.cline)
109 | # If we find none, the remainder of the string can be pushed to
110 | # the current argument and the argument finalized
111 | if not m:
112 | self._push(self.cline)
113 | break
114 | # The beginning of the string, up to the found token, is part of
115 | # the current argument
116 | if m.start():
117 | self._push(self.cline[:m.start()])
118 | self.cline = self.cline[m.end():]
119 |
120 | match = dict([(name, value) for name, value in m.groupdict().items() if value])
121 | if 'quote' in match:
122 | # " or ' start a quoted string
123 | if match['quote'] == '"':
124 | self._parse_doubly_quoted()
125 | else:
126 | self._parse_quoted()
127 | elif 'comment' in match:
128 | # Comments are ignored. The current argument can be finalized,
129 | # and parsing stopped.
130 | break
131 | elif 'special' in match:
132 | # Unquoted, non-escaped special characters need to be sent to a
133 | # shell.
134 | raise MetaCharacterException(match['special'])
135 | elif 'whitespace' in match:
136 | # Whitespaces terminate current argument.
137 | self._next()
138 | elif 'escape' in match:
139 | # Escaped backslashes turn into a single backslash
140 | self._push('\\')
141 | elif 'backslashed' in match:
142 | # Backslashed characters are unbackslashed
143 | # e.g. echo \a -> a
144 | self._push(match['backslashed'][1])
145 | elif 'glob' in match:
146 | # ? or * will need globbing
147 | self.glob = True
148 | self._push(m.group(0))
149 | else:
150 | raise Exception("Shouldn't reach here")
151 | if self.arg:
152 | self._next()
153 |
154 | def _parse_quoted(self):
155 | # Single quoted strings are preserved, except for the final quote
156 | index = self.cline.find("'")
157 | if index == -1:
158 | raise Exception('Unterminated quoted string in command')
159 | self._push(self.cline[:index])
160 | self.cline = self.cline[index+1:]
161 |
162 | def _parse_doubly_quoted(self):
163 | if not self.cline:
164 | raise Exception('Unterminated quoted string in command')
165 | while self.cline:
166 | m = _doubly_quoted_tokens.search(self.cline)
167 | if not m:
168 | raise Exception('Unterminated quoted string in command')
169 | self._push(self.cline[:m.start()])
170 | self.cline = self.cline[m.end():]
171 | match = dict([(name, value) for name, value in m.groupdict().items() if value])
172 | if 'quote' in match:
173 | # a double quote ends the quoted string, so go back to
174 | # unquoted parsing
175 | return
176 | elif 'special' in match:
177 | # Unquoted, non-escaped special characters in a doubly quoted
178 | # string still have a special meaning and need to be sent to a
179 | # shell.
180 | raise MetaCharacterException(match['special'])
181 | elif 'escape' in match:
182 | # Escaped backslashes turn into a single backslash
183 | self._push('\\')
184 | elif 'backslashedquote' in match:
185 | # Backslashed double quotes are un-backslashed
186 | self._push('"')
187 | elif 'backslashed' in match:
188 | # Backslashed characters are kept backslashed
189 | self._push(match['backslashed'])
190 |
191 | def clinetoargv(cline, cwd):
192 | """
193 | If this command line can safely skip the shell, return an argv array.
194 | @returns argv, badchar
195 | """
196 | str = _escapednewlines.sub('', cline)
197 | try:
198 | args = ClineSplitter(str, cwd)
199 | except MetaCharacterException as e:
200 | return None, e.char
201 |
202 | if len(args) and args[0].find('=') != -1:
203 | return None, '='
204 |
205 | return args, None
206 |
207 | # shellwords contains a set of shell builtin commands that need to be
208 | # executed within a shell. It also contains a set of commands that are known
209 | # to be giving problems when run directly instead of through the msys shell.
210 | shellwords = (':', '.', 'break', 'cd', 'continue', 'exec', 'exit', 'export',
211 | 'getopts', 'hash', 'pwd', 'readonly', 'return', 'shift',
212 | 'test', 'times', 'trap', 'umask', 'unset', 'alias',
213 | 'set', 'bind', 'builtin', 'caller', 'command', 'declare',
214 | 'echo', 'enable', 'help', 'let', 'local', 'logout',
215 | 'printf', 'read', 'shopt', 'source', 'type', 'typeset',
216 | 'ulimit', 'unalias', 'set', 'find')
217 |
218 | def prepare_command(cline, cwd, loc):
219 | """
220 | Returns a list of command and arguments for the given command line string.
221 | If the command needs to be run through a shell for some reason, the
222 | returned list contains the shell invocation.
223 | """
224 |
225 | #TODO: call this once up-front somewhere and save the result?
226 | shell, msys = util.checkmsyscompat()
227 |
228 | shellreason = None
229 | executable = None
230 | if msys and cline.startswith('/'):
231 | shellreason = "command starts with /"
232 | else:
233 | argv, badchar = clinetoargv(cline, cwd)
234 | if argv is None:
235 | shellreason = "command contains shell-special character '%s'" % (badchar,)
236 | elif len(argv) and argv[0] in shellwords:
237 | shellreason = "command starts with shell primitive '%s'" % (argv[0],)
238 | elif argv and (os.sep in argv[0] or os.altsep and os.altsep in argv[0]):
239 | executable = util.normaljoin(cwd, argv[0])
240 | # Avoid "%1 is not a valid Win32 application" errors, assuming
241 | # that if the executable path is to be resolved with PATH, it will
242 | # be a Win32 executable.
243 | if sys.platform == 'win32' and os.path.isfile(executable) and open(executable, 'rb').read(2) == "#!":
244 | shellreason = "command executable starts with a hashbang"
245 |
246 | if shellreason is not None:
247 | _log.debug("%s: using shell: %s: '%s'", loc, shellreason, cline)
248 | if msys:
249 | if len(cline) > 3 and cline[1] == ':' and cline[2] == '/':
250 | cline = '/' + cline[0] + cline[2:]
251 | argv = [shell, "-c", cline]
252 | executable = None
253 |
254 | return executable, argv
255 |
256 | def call(cline, env, cwd, loc, cb, context, echo, justprint=False):
257 | executable, argv = prepare_command(cline, cwd, loc)
258 |
259 | if not len(argv):
260 | cb(res=0)
261 | return
262 |
263 | if argv[0] == command.makepypath:
264 | command.main(argv[1:], env, cwd, cb)
265 | return
266 |
267 | if argv[0:2] == [sys.executable.replace('\\', '/'),
268 | command.makepypath.replace('\\', '/')]:
269 | command.main(argv[2:], env, cwd, cb)
270 | return
271 |
272 | context.call(argv, executable=executable, shell=False, env=env, cwd=cwd, cb=cb,
273 | echo=echo, justprint=justprint)
274 |
275 | def call_native(module, method, argv, env, cwd, loc, cb, context, echo, justprint=False,
276 | pycommandpath=None):
277 | context.call_native(module, method, argv, env=env, cwd=cwd, cb=cb,
278 | echo=echo, justprint=justprint, pycommandpath=pycommandpath)
279 |
280 | def statustoresult(status):
281 | """
282 | Convert the status returned from waitpid into a prettier numeric result.
283 | """
284 | sig = status & 0xFF
285 | if sig:
286 | return -sig
287 |
288 | return status >>8
289 |
290 | class Job(object):
291 | """
292 | A single job to be executed on the process pool.
293 | """
294 | done = False # set to true when the job completes
295 |
296 | def __init__(self):
297 | self.exitcode = -127
298 |
299 | def notify(self, condition, result):
300 | condition.acquire()
301 | self.done = True
302 | self.exitcode = result
303 | condition.notify()
304 | condition.release()
305 |
306 | def get_callback(self, condition):
307 | return lambda result: self.notify(condition, result)
308 |
309 | class PopenJob(Job):
310 | """
311 | A job that executes a command using subprocess.Popen.
312 | """
313 | def __init__(self, argv, executable, shell, env, cwd):
314 | Job.__init__(self)
315 | self.argv = argv
316 | self.executable = executable
317 | self.shell = shell
318 | self.env = env
319 | self.cwd = cwd
320 | self.parentpid = os.getpid()
321 |
322 | def run(self):
323 | assert os.getpid() != self.parentpid
324 | # subprocess.Popen doesn't use the PATH set in the env argument for
325 | # finding the executable on some platforms (but strangely it does on
326 | # others!), so set os.environ['PATH'] explicitly. This is parallel-
327 | # safe because pymake uses separate processes for parallelism, and
328 | # each process is serial. See http://bugs.python.org/issue8557 for a
329 | # general overview of "subprocess PATH semantics and portability".
330 | oldpath = os.environ['PATH']
331 | try:
332 | if self.env is not None and 'PATH' in self.env:
333 | os.environ['PATH'] = self.env['PATH']
334 | p = subprocess.Popen(self.argv, executable=self.executable, shell=self.shell, env=self.env, cwd=self.cwd)
335 | return p.wait()
336 | except OSError as e:
337 | print(e, file=sys.stderr)
338 | return -127
339 | finally:
340 | os.environ['PATH'] = oldpath
341 |
342 | class PythonJob(Job):
343 | """
344 | A job that calls a Python method.
345 | """
346 | def __init__(self, module, method, argv, env, cwd, pycommandpath=None):
347 | self.module = module
348 | self.method = method
349 | self.argv = argv
350 | self.env = env
351 | self.cwd = cwd
352 | self.pycommandpath = pycommandpath or []
353 | self.parentpid = os.getpid()
354 |
355 | def run(self):
356 | assert os.getpid() != self.parentpid
357 | # os.environ is a magic dictionary. Setting it to something else
358 | # doesn't affect the environment of subprocesses, so use clear/update
359 | oldenv = dict(os.environ)
360 |
361 | # sys.path is adjusted for the entire lifetime of the command
362 | # execution. This ensures any delayed imports will still work.
363 | oldsyspath = list(sys.path)
364 | try:
365 | os.chdir(self.cwd)
366 | os.environ.clear()
367 | os.environ.update(self.env)
368 |
369 | sys.path = []
370 | for p in sys.path + self.pycommandpath:
371 | site.addsitedir(p)
372 | sys.path.extend(oldsyspath)
373 |
374 | if self.module not in sys.modules:
375 | try:
376 | __import__(self.module)
377 | except Exception as e:
378 | print('Error importing %s: %s' % (
379 | self.module, e), file=sys.stderr)
380 | return -127
381 |
382 | m = sys.modules[self.module]
383 | if self.method not in m.__dict__:
384 | print("No method named '%s' in module %s" % (self.method, self.module), file=sys.stderr)
385 | return -127
386 | rv = m.__dict__[self.method](self.argv)
387 | if rv != 0 and rv is not None:
388 | print((
389 | "Native command '%s %s' returned value '%s'" %
390 | (self.module, self.method, rv)), file=sys.stderr)
391 | return (rv if isinstance(rv, int) else 1)
392 |
393 | except errors.PythonError as e:
394 | print(e, file=sys.stderr)
395 | return e.exitcode
396 | except:
397 | e = sys.exc_info()[1]
398 | if isinstance(e, SystemExit) and (e.code == 0 or e.code is None):
399 | pass # sys.exit(0) is not a failure
400 | else:
401 | print(e, file=sys.stderr)
402 | traceback.print_exc()
403 | return -127
404 | finally:
405 | os.environ.clear()
406 | os.environ.update(oldenv)
407 | sys.path = oldsyspath
408 | # multiprocessing exits via os._exit, make sure that all output
409 | # from command gets written out before that happens.
410 | sys.stdout.flush()
411 | sys.stderr.flush()
412 |
413 | return 0
414 |
415 | def job_runner(job):
416 | """
417 | Run a job. Called in a Process pool.
418 | """
419 | return job.run()
420 |
421 | class ParallelContext(object):
422 | """
423 | Manages the parallel execution of processes.
424 | """
425 |
426 | _allcontexts = set()
427 | _condition = multiprocessing.Condition()
428 |
429 | def __init__(self, jcount):
430 | self.jcount = jcount
431 | self.exit = False
432 |
433 | self.processpool = multiprocessing.Pool(processes=jcount)
434 | self.pending = deque() # deque of (cb, args, kwargs)
435 | self.running = [] # list of (subprocess, cb)
436 |
437 | self._allcontexts.add(self)
438 |
439 | def finish(self):
440 | assert len(self.pending) == 0 and len(self.running) == 0, "pending: %i running: %i" % (len(self.pending), len(self.running))
441 | self.processpool.close()
442 | self.processpool.join()
443 | self._allcontexts.remove(self)
444 |
445 | def run(self):
446 | while len(self.pending) and len(self.running) < self.jcount:
447 | cb, args, kwargs = self.pending.popleft()
448 | cb(*args, **kwargs)
449 |
450 | def defer(self, cb, *args, **kwargs):
451 | assert self.jcount > 1 or not len(self.pending), "Serial execution error defering %r %r %r: currently pending %r" % (cb, args, kwargs, self.pending)
452 | self.pending.append((cb, args, kwargs))
453 |
454 | def _docall_generic(self, pool, job, cb, echo, justprint):
455 | if echo is not None:
456 | print(echo)
457 | processcb = job.get_callback(ParallelContext._condition)
458 | if justprint:
459 | processcb(0)
460 | else:
461 | pool.apply_async(job_runner, args=(job,), callback=processcb)
462 | self.running.append((job, cb))
463 |
464 | def call(self, argv, shell, env, cwd, cb, echo, justprint=False, executable=None):
465 | """
466 | Asynchronously call the process
467 | """
468 |
469 | job = PopenJob(argv, executable=executable, shell=shell, env=env, cwd=cwd)
470 | self.defer(self._docall_generic, self.processpool, job, cb, echo, justprint)
471 |
472 | def call_native(self, module, method, argv, env, cwd, cb,
473 | echo, justprint=False, pycommandpath=None):
474 | """
475 | Asynchronously call the native function
476 | """
477 |
478 | job = PythonJob(module, method, argv, env, cwd, pycommandpath)
479 | self.defer(self._docall_generic, self.processpool, job, cb, echo, justprint)
480 |
481 | @staticmethod
482 | def _waitany(condition):
483 | def _checkdone():
484 | jobs = []
485 | for c in ParallelContext._allcontexts:
486 | for i in range(0, len(c.running)):
487 | if c.running[i][0].done:
488 | jobs.append(c.running[i])
489 | for j in jobs:
490 | if j in c.running:
491 | c.running.remove(j)
492 | return jobs
493 |
494 | # We must acquire the lock, and then check to see if any jobs have
495 | # finished. If we don't check after acquiring the lock it's possible
496 | # that all outstanding jobs will have completed before we wait and we'll
497 | # wait for notifications that have already occurred.
498 | condition.acquire()
499 | jobs = _checkdone()
500 |
501 | if jobs == []:
502 | condition.wait()
503 | jobs = _checkdone()
504 |
505 | condition.release()
506 |
507 | return jobs
508 |
509 | @staticmethod
510 | def spin():
511 | """
512 | Spin the 'event loop', and never return.
513 | """
514 |
515 | while True:
516 | clist = list(ParallelContext._allcontexts)
517 | for c in clist:
518 | c.run()
519 |
520 | dowait = util.any((len(c.running) for c in ParallelContext._allcontexts))
521 | if dowait:
522 | # Wait on local jobs first for perf
523 | for job, cb in ParallelContext._waitany(ParallelContext._condition):
524 | cb(job.exitcode)
525 | else:
526 | assert any(len(c.pending) for c in ParallelContext._allcontexts)
527 |
528 | def makedeferrable(usercb, **userkwargs):
529 | def cb(*args, **kwargs):
530 | kwargs.update(userkwargs)
531 | return usercb(*args, **kwargs)
532 |
533 | return cb
534 |
535 | _serialContext = None
536 | _parallelContext = None
537 |
538 | def getcontext(jcount):
539 | global _serialContext, _parallelContext
540 | if jcount == 1:
541 | if _serialContext is None:
542 | _serialContext = ParallelContext(1)
543 | return _serialContext
544 | else:
545 | if _parallelContext is None:
546 | _parallelContext = ParallelContext(jcount)
547 | return _parallelContext
548 |
549 |
--------------------------------------------------------------------------------
/pymake/util.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | def normaljoin(path, suffix):
4 | """
5 | Combine the given path with the suffix, and normalize if necessary to shrink the path to avoid hitting path length limits
6 | """
7 | result = os.path.join(path, suffix)
8 | if len(result) > 255:
9 | result = os.path.normpath(result)
10 | return result
11 |
12 | def joiniter(fd, it):
13 | """
14 | Given an iterator that returns strings, write the words with a space in between each.
15 | """
16 |
17 | it = iter(it)
18 | for i in it:
19 | fd.write(i)
20 | break
21 |
22 | for i in it:
23 | fd.write(' ')
24 | fd.write(i)
25 |
26 | def checkmsyscompat():
27 | """For msys compatibility on windows, honor the SHELL environment variable,
28 | and if $MSYSTEM == MINGW32, run commands through $SHELL -c instead of
29 | letting Python use the system shell."""
30 | if 'SHELL' in os.environ:
31 | shell = os.environ['SHELL']
32 | elif 'MOZILLABUILD' in os.environ:
33 | shell = os.environ['MOZILLABUILD'] + '/msys/bin/sh.exe'
34 | elif 'COMSPEC' in os.environ:
35 | shell = os.environ['COMSPEC']
36 | else:
37 | raise DataError("Can't find a suitable shell!")
38 |
39 | msys = False
40 | if 'MSYSTEM' in os.environ and os.environ['MSYSTEM'] == 'MINGW32':
41 | msys = True
42 | if not shell.lower().endswith(".exe"):
43 | shell += ".exe"
44 | return (shell, msys)
45 |
46 | if hasattr(str, 'partition'):
47 | def strpartition(str, token):
48 | return str.partition(token)
49 |
50 | def strrpartition(str, token):
51 | return str.rpartition(token)
52 |
53 | else:
54 | def strpartition(str, token):
55 | """Python 2.4 compatible str.partition"""
56 |
57 | offset = str.find(token)
58 | if offset == -1:
59 | return str, '', ''
60 |
61 | return str[:offset], token, str[offset + len(token):]
62 |
63 | def strrpartition(str, token):
64 | """Python 2.4 compatible str.rpartition"""
65 |
66 | offset = str.rfind(token)
67 | if offset == -1:
68 | return '', '', str
69 |
70 | return str[:offset], token, str[offset + len(token):]
71 |
72 | try:
73 | from __builtin__ import any
74 | except ImportError:
75 | def any(it):
76 | for i in it:
77 | if i:
78 | return True
79 | return False
80 |
81 | class _MostUsedItem(object):
82 | __slots__ = ('key', 'o', 'count')
83 |
84 | def __init__(self, key):
85 | self.key = key
86 | self.o = None
87 | self.count = 1
88 |
89 | def __repr__(self):
90 | return "MostUsedItem(key=%r, count=%i, o=%r)" % (self.key, self.count, self.o)
91 |
92 | class MostUsedCache(object):
93 | def __init__(self, capacity, creationfunc, verifyfunc):
94 | self.capacity = capacity
95 | self.cfunc = creationfunc
96 | self.vfunc = verifyfunc
97 |
98 | self.d = {}
99 | self.active = [] # lazily sorted!
100 |
101 | def setactive(self, item):
102 | if item in self.active:
103 | return
104 |
105 | if len(self.active) == self.capacity:
106 | self.active.sort(key=lambda i: i.count)
107 | old = self.active.pop(0)
108 | old.o = None
109 | # print "Evicting %s" % old.key
110 |
111 | self.active.append(item)
112 |
113 | def get(self, key):
114 | item = self.d.get(key, None)
115 | if item is None:
116 | item = _MostUsedItem(key)
117 | self.d[key] = item
118 | else:
119 | item.count += 1
120 |
121 | if item.o is not None and self.vfunc(key, item.o):
122 | return item.o
123 |
124 | item.o = self.cfunc(key)
125 | self.setactive(item)
126 | return item.o
127 |
128 | def verify(self):
129 | for k, v in self.d.items():
130 | if v.o:
131 | assert v in self.active
132 | else:
133 | assert v not in self.active
134 |
135 | def debugitems(self):
136 | l = [i.key for i in self.active]
137 | l.sort()
138 | return l
139 |
--------------------------------------------------------------------------------
/pymake/win32process.py:
--------------------------------------------------------------------------------
1 | from ctypes import windll, POINTER, byref, WinError, WINFUNCTYPE
2 | from ctypes.wintypes import HANDLE, DWORD, BOOL
3 |
4 | INFINITE = -1
5 | WAIT_FAILED = 0xFFFFFFFF
6 |
7 | LPDWORD = POINTER(DWORD)
8 | _GetExitCodeProcessProto = WINFUNCTYPE(BOOL, HANDLE, LPDWORD)
9 | _GetExitCodeProcess = _GetExitCodeProcessProto(("GetExitCodeProcess", windll.kernel32))
10 | def GetExitCodeProcess(h):
11 | exitcode = DWORD()
12 | r = _GetExitCodeProcess(h, byref(exitcode))
13 | if r is 0:
14 | raise WinError()
15 | return exitcode.value
16 |
17 | _WaitForMultipleObjectsProto = WINFUNCTYPE(DWORD, DWORD, POINTER(HANDLE), BOOL, DWORD)
18 | _WaitForMultipleObjects = _WaitForMultipleObjectsProto(("WaitForMultipleObjects", windll.kernel32))
19 |
20 | def WaitForAnyProcess(processes):
21 | arrtype = HANDLE * len(processes)
22 | harray = arrtype(*(int(p._handle) for p in processes))
23 |
24 | r = _WaitForMultipleObjects(len(processes), harray, False, INFINITE)
25 | if r == WAIT_FAILED:
26 | raise WinError()
27 |
28 | return processes[r], GetExitCodeProcess(int(processes[r]._handle)) <<8
29 |
--------------------------------------------------------------------------------
/tests/automatic-variables.mk:
--------------------------------------------------------------------------------
1 | $(shell \
2 | mkdir -p src/subd; \
3 | mkdir subd; \
4 | touch dummy; \
5 | sleep 2; \
6 | touch subd/test.out src/subd/test.in2; \
7 | sleep 2; \
8 | touch subd/test.out2 src/subd/test.in; \
9 | sleep 2; \
10 | touch subd/host_test.out subd/host_test.out2; \
11 | sleep 2; \
12 | touch host_prog; \
13 | )
14 |
15 | VPATH = src
16 |
17 | all: prog host_prog prog dir/
18 | test "$@" = "all"
19 | test "$<" = "prog"
20 | test "$^" = "prog host_prog dir"
21 | test "$?" = "prog host_prog dir"
22 | test "$+" = "prog host_prog prog dir"
23 | test "$(@D)" = "."
24 | test "$(@F)" = "all"
25 | test "$($@
75 |
76 | %.out2: %.in2 dummy
77 | @echo TEST_FAIL No need to remake
78 |
79 | .PHONY: all
80 |
--------------------------------------------------------------------------------
/tests/bad-command-continuation.mk:
--------------------------------------------------------------------------------
1 | all:
2 | echo 'hello'\
3 | TEST-PASS
4 |
--------------------------------------------------------------------------------
/tests/call.mk:
--------------------------------------------------------------------------------
1 | test = $0
2 | reverse = $2 $1
3 | twice = $1$1
4 | sideeffect = $(shell echo "called$1:" >>dummyfile)
5 |
6 | all:
7 | test "$(call test)" = "test"
8 | test "$(call reverse,1,2)" = "2 1"
9 | # expansion happens *before* substitution, thank sanity
10 | test "$(call twice,$(sideeffect))" = ""
11 | test `cat dummyfile` = "called:"
12 | @echo TEST-PASS
13 |
--------------------------------------------------------------------------------
/tests/cmd-stripdotslash.mk:
--------------------------------------------------------------------------------
1 | all:
2 | $(MAKE) -f $(TESTPATH)/cmd-stripdotslash.mk ./foo
3 |
4 | ./foo:
5 | @echo TEST-PASS
6 |
--------------------------------------------------------------------------------
/tests/cmdgoals.mk:
--------------------------------------------------------------------------------
1 | default:
2 | test "$(MAKECMDGOALS)" = ""
3 | $(MAKE) -f $(TESTPATH)/cmdgoals.mk t1 t2
4 | @echo TEST-PASS
5 |
6 | t1:
7 | test "$(MAKECMDGOALS)" = "t1 t2"
8 |
9 | t2:
10 |
--------------------------------------------------------------------------------
/tests/commandmodifiers.mk:
--------------------------------------------------------------------------------
1 | define COMMAND
2 | $(1)
3 | $(1)
4 |
5 | endef
6 |
7 | all:
8 | $(call COMMAND,@true #TEST-FAIL)
9 | $(call COMMAND,-exit 4)
10 | $(call COMMAND,@-exit 1 # TEST-FAIL)
11 | $(call COMMAND,-@exit 1 # TEST-FAIL)
12 | $(call COMMAND,+exit 0)
13 | $(call COMMAND,+-exit 1)
14 | $(call COMMAND,@+exit 0 # TEST-FAIL)
15 | $(call COMMAND,+@exit 0 # TEST-FAIL)
16 | $(call COMMAND,-+@exit 1 # TEST-FAIL)
17 | $(call COMMAND,+-@exit 1 # TEST-FAIL)
18 | $(call COMMAND,@+-exit 1 # TEST-FAIL)
19 | $(call COMMAND,@+-@+-exit 1 # TEST-FAIL)
20 | $(call COMMAND,@@++exit 0 # TEST-FAIL)
21 | @echo TEST-PASS
22 |
--------------------------------------------------------------------------------
/tests/comment-parsing.mk:
--------------------------------------------------------------------------------
1 | # where do comments take effect?
2 |
3 | VAR = val1 # comment
4 | VAR2 = lit2\#hash
5 | VAR2_1 = lit2.1\\\#hash
6 | VAR3 = val3
7 | VAR4 = lit4\\#backslash
8 | VAR4_1 = lit4\\\\#backslash
9 | VAR5 = lit5\char
10 | VAR6 = lit6\\char
11 | VAR7 = lit7\\
12 | VAR8 = lit8\\\\
13 | VAR9 = lit9\\\\extra
14 | # This comment extends to the next line \
15 | VAR3 = ignored
16 |
17 | all:
18 | test "$(VAR)" = "val1 "
19 | test "$(VAR2)" = "lit2#hash"
20 | test '$(VAR2_1)' = 'lit2.1\#hash'
21 | test "$(VAR3)" = "val3"
22 | test '$(VAR4)' = 'lit4\'
23 | test '$(VAR4_1)' = 'lit4\\'
24 | test '$(VAR5)' = 'lit5\char'
25 | test '$(VAR6)' = 'lit6\\char'
26 | test '$(VAR7)' = 'lit7\\'
27 | test '$(VAR8)' = 'lit8\\\\'
28 | test '$(VAR9)' = 'lit9\\\\extra'
29 | @echo "TEST-PASS"
30 |
--------------------------------------------------------------------------------
/tests/continuations-in-functions.mk:
--------------------------------------------------------------------------------
1 | all:
2 | test 'Hello world.' = '$(if 1,Hello \
3 | world.)'
4 | test '(Hello world.)' != '(Hello \
5 | world.)'
6 | @echo TEST-PASS
7 |
--------------------------------------------------------------------------------
/tests/datatests.py:
--------------------------------------------------------------------------------
1 | import pymake.data, pymake.functions, pymake.util
2 | import unittest
3 | import re
4 |
5 |
6 | def multitest(cls):
7 | for name in cls.testdata.keys():
8 | def m(self, name=name):
9 | return self.runSingle(*self.testdata[name])
10 |
11 | setattr(cls, 'test_%s' % name, m)
12 | return cls
13 |
14 | class SplitWordsTest(unittest.TestCase):
15 | testdata = (
16 | (' test test.c test.o ', ['test', 'test.c', 'test.o']),
17 | ('\ttest\t test.c \ntest.o', ['test', 'test.c', 'test.o']),
18 | )
19 |
20 | def runTest(self):
21 | for s, e in self.testdata:
22 | w = s.split()
23 | self.assertEqual(w, e, 'splitwords(%r)' % (s,))
24 |
25 | class GetPatSubstTest(unittest.TestCase):
26 | testdata = (
27 | ('%.c', '%.o', ' test test.c test.o ', 'test test.o test.o'),
28 | ('%', '%.o', ' test.c test.o ', 'test.c.o test.o.o'),
29 | ('foo', 'bar', 'test foo bar', 'test bar bar'),
30 | ('foo', '%bar', 'test foo bar', 'test %bar bar'),
31 | ('%', 'perc_%', 'path', 'perc_path'),
32 | ('\\%', 'sub%', 'p %', 'p sub%'),
33 | ('%.c', '\\%%.o', 'foo.c bar.o baz.cpp', '%foo.o bar.o baz.cpp'),
34 | )
35 |
36 | def runTest(self):
37 | for s, r, d, e in self.testdata:
38 | words = d.split()
39 | p = pymake.data.Pattern(s)
40 | a = ' '.join((p.subst(r, word, False)
41 | for word in words))
42 | self.assertEqual(a, e, 'Pattern(%r).subst(%r, %r)' % (s, r, d))
43 |
44 | class LRUTest(unittest.TestCase):
45 | # getkey, expected, funccount, debugitems
46 | expected = (
47 | (0, '', 1, (0,)),
48 | (0, '', 2, (0,)),
49 | (1, ' ', 3, (1, 0)),
50 | (1, ' ', 3, (1, 0)),
51 | (0, '', 4, (0, 1)),
52 | (2, ' ', 5, (2, 0, 1)),
53 | (1, ' ', 5, (1, 2, 0)),
54 | (3, ' ', 6, (3, 1, 2)),
55 | )
56 |
57 | def spaceFunc(self, l):
58 | self.funccount += 1
59 | return ''.ljust(l)
60 |
61 | def runTest(self):
62 | self.funccount = 0
63 | c = pymake.util.LRUCache(3, self.spaceFunc, lambda k, v: k % 2)
64 | self.assertEqual(tuple(c.debugitems()), ())
65 |
66 | for i in range(0, len(self.expected)):
67 | k, e, fc, di = self.expected[i]
68 |
69 | v = c.get(k)
70 | self.assertEqual(v, e)
71 | self.assertEqual(self.funccount, fc,
72 | "funccount, iteration %i, got %i expected %i" % (i, self.funccount, fc))
73 | goti = tuple(c.debugitems())
74 | self.assertEqual(goti, di,
75 | "debugitems, iteration %i, got %r expected %r" % (i, goti, di))
76 |
77 | class EqualityTest(unittest.TestCase):
78 | def test_string_expansion(self):
79 | s1 = pymake.data.StringExpansion('foo bar', None)
80 | s2 = pymake.data.StringExpansion('foo bar', None)
81 |
82 | self.assertEqual(s1, s2)
83 |
84 | def test_expansion_simple(self):
85 | s1 = pymake.data.Expansion(None)
86 | s2 = pymake.data.Expansion(None)
87 |
88 | self.assertEqual(s1, s2)
89 |
90 | s1.appendstr('foo')
91 | s2.appendstr('foo')
92 | self.assertEqual(s1, s2)
93 |
94 | def test_expansion_string_finish(self):
95 | """Adjacent strings should normalize to same value."""
96 | s1 = pymake.data.Expansion(None)
97 | s2 = pymake.data.Expansion(None)
98 |
99 | s1.appendstr('foo')
100 | s2.appendstr('foo')
101 |
102 | s1.appendstr(' bar')
103 | s1.appendstr(' baz')
104 | s2.appendstr(' bar baz')
105 |
106 | self.assertEqual(s1, s2)
107 |
108 | def test_function(self):
109 | s1 = pymake.data.Expansion(None)
110 | s2 = pymake.data.Expansion(None)
111 |
112 | n1 = pymake.data.StringExpansion('FOO', None)
113 | n2 = pymake.data.StringExpansion('FOO', None)
114 |
115 | v1 = pymake.functions.VariableRef(None, n1)
116 | v2 = pymake.functions.VariableRef(None, n2)
117 |
118 | s1.appendfunc(v1)
119 | s2.appendfunc(v2)
120 |
121 | self.assertEqual(s1, s2)
122 |
123 |
124 | class StringExpansionTest(unittest.TestCase):
125 | def test_base_expansion_interface(self):
126 | s1 = pymake.data.StringExpansion('FOO', None)
127 |
128 | self.assertTrue(s1.is_static_string)
129 |
130 | funcs = list(s1.functions())
131 | self.assertEqual(len(funcs), 0)
132 |
133 | funcs = list(s1.functions(True))
134 | self.assertEqual(len(funcs), 0)
135 |
136 | refs = list(s1.variable_references())
137 | self.assertEqual(len(refs), 0)
138 |
139 |
140 | class ExpansionTest(unittest.TestCase):
141 | def test_is_static_string(self):
142 | e1 = pymake.data.Expansion()
143 | e1.appendstr('foo')
144 |
145 | self.assertTrue(e1.is_static_string)
146 |
147 | e1.appendstr('bar')
148 | self.assertTrue(e1.is_static_string)
149 |
150 | vname = pymake.data.StringExpansion('FOO', None)
151 | func = pymake.functions.VariableRef(None, vname)
152 |
153 | e1.appendfunc(func)
154 |
155 | self.assertFalse(e1.is_static_string)
156 |
157 | def test_get_functions(self):
158 | e1 = pymake.data.Expansion()
159 | e1.appendstr('foo')
160 |
161 | vname1 = pymake.data.StringExpansion('FOO', None)
162 | vname2 = pymake.data.StringExpansion('BAR', None)
163 |
164 | func1 = pymake.functions.VariableRef(None, vname1)
165 | func2 = pymake.functions.VariableRef(None, vname2)
166 |
167 | e1.appendfunc(func1)
168 | e1.appendfunc(func2)
169 |
170 | funcs = list(e1.functions())
171 | self.assertEqual(len(funcs), 2)
172 |
173 | func3 = pymake.functions.SortFunction(None)
174 | func3.append(vname1)
175 |
176 | e1.appendfunc(func3)
177 |
178 | funcs = list(e1.functions())
179 | self.assertEqual(len(funcs), 3)
180 |
181 | refs = list(e1.variable_references())
182 | self.assertEqual(len(refs), 2)
183 |
184 | def test_get_functions_descend(self):
185 | e1 = pymake.data.Expansion()
186 | vname1 = pymake.data.StringExpansion('FOO', None)
187 | func1 = pymake.functions.VariableRef(None, vname1)
188 | e2 = pymake.data.Expansion()
189 | e2.appendfunc(func1)
190 |
191 | func2 = pymake.functions.SortFunction(None)
192 | func2.append(e2)
193 |
194 | e1.appendfunc(func2)
195 |
196 | funcs = list(e1.functions())
197 | self.assertEqual(len(funcs), 1)
198 |
199 | funcs = list(e1.functions(True))
200 | self.assertEqual(len(funcs), 2)
201 |
202 | self.assertTrue(isinstance(funcs[0], pymake.functions.SortFunction))
203 |
204 | def test_is_filesystem_dependent(self):
205 | e = pymake.data.Expansion()
206 | vname1 = pymake.data.StringExpansion('FOO', None)
207 | func1 = pymake.functions.VariableRef(None, vname1)
208 | e.appendfunc(func1)
209 |
210 | self.assertFalse(e.is_filesystem_dependent)
211 |
212 | func2 = pymake.functions.WildcardFunction(None)
213 | func2.append(vname1)
214 | e.appendfunc(func2)
215 |
216 | self.assertTrue(e.is_filesystem_dependent)
217 |
218 | def test_is_filesystem_dependent_descend(self):
219 | sort = pymake.functions.SortFunction(None)
220 | wildcard = pymake.functions.WildcardFunction(None)
221 |
222 | e = pymake.data.StringExpansion('foo/*', None)
223 | wildcard.append(e)
224 |
225 | e = pymake.data.Expansion(None)
226 | e.appendfunc(wildcard)
227 |
228 | sort.append(e)
229 |
230 | e = pymake.data.Expansion(None)
231 | e.appendfunc(sort)
232 |
233 | self.assertTrue(e.is_filesystem_dependent)
234 |
235 |
236 | if __name__ == '__main__':
237 | unittest.main()
238 |
--------------------------------------------------------------------------------
/tests/default-goal-set-first.mk:
--------------------------------------------------------------------------------
1 | .DEFAULT_GOAL := default
2 |
3 | not-default:
4 | @echo TEST-FAIL did not run default rule
5 |
6 | default:
7 | @echo TEST-PASS
8 |
--------------------------------------------------------------------------------
/tests/default-goal.mk:
--------------------------------------------------------------------------------
1 | not-default:
2 | @echo TEST-FAIL did not run default rule
3 |
4 | default:
5 | @echo $(if $(filter not-default,$(INTERMEDIATE_DEFAULT_GOAL)),TEST-PASS,TEST-FAIL .DEFAULT_GOAL not set by $(MAKE))
6 |
7 | INTERMEDIATE_DEFAULT_GOAL := $(.DEFAULT_GOAL)
8 | .DEFAULT_GOAL := default
9 |
--------------------------------------------------------------------------------
/tests/default-target.mk:
--------------------------------------------------------------------------------
1 | test: VAR = value
2 |
3 | %.do:
4 | @echo TEST-FAIL: ran target "$@", should have run "all"
5 |
6 | .PHONY: test
7 |
8 | all:
9 | @echo TEST-PASS: the default target is all
10 |
11 | test:
12 | @echo TEST-FAIL: ran target "$@", should have run "all"
13 |
14 | test.do:
15 |
--------------------------------------------------------------------------------
/tests/default-target2.mk:
--------------------------------------------------------------------------------
1 | test.foo: %.foo:
2 | test "$@" = "test.foo"
3 | @echo TEST-PASS made test.foo by default
4 |
5 | all:
6 | @echo TEST-FAIL made $@, should have made test.foo
7 |
--------------------------------------------------------------------------------
/tests/define-directive.mk:
--------------------------------------------------------------------------------
1 | define COMMANDS
2 | shellvar=hello
3 | test "$$shellvar" != "hello"
4 | endef
5 |
6 | define COMMANDS2
7 | shellvar=hello; \
8 | test "$$shellvar" = "hello"
9 | endef
10 |
11 | define VARWITHCOMMENT # comment
12 | value
13 | endef
14 |
15 | define TEST3
16 | whitespace
17 | endef
18 |
19 | define TEST4
20 | define TEST5
21 | random
22 | endef
23 | endef
24 |
25 | ifdef TEST5
26 | $(error TEST5 should not be set)
27 | endif
28 |
29 | define TEST6
30 | define TEST7
31 | random
32 | endef
33 | endef
34 |
35 | ifdef TEST7
36 | $(error TEST7 should not be set)
37 | endif
38 |
39 | define TEST8
40 | is this # a comment?
41 | endef
42 |
43 | ifneq ($(TEST8),is this \# a comment?)
44 | $(error TEST8 value not expected: $(TEST8))
45 | endif
46 |
47 | # A backslash continuation "hides" the endef
48 | define TEST9
49 | value \
50 | endef
51 | endef
52 |
53 | # Test ridiculous spacing
54 | define TEST10
55 | define TEST11
56 | baz
57 | endef
58 | define TEST12
59 | foo
60 | endef
61 | endef
62 |
63 | all:
64 | $(COMMANDS)
65 | $(COMMANDS2)
66 | test '$(VARWITHCOMMENT)' = 'value'
67 | test '$(COMMANDS2)' = 'shellvar=hello; test "$$shellvar" = "hello"'
68 | test "$(TEST3)" = " whitespace"
69 | @echo TEST-PASS
70 |
--------------------------------------------------------------------------------
/tests/depfailed.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | all: foo.out foo.in
4 | @echo TEST-PASS
5 |
--------------------------------------------------------------------------------
/tests/depfailedj.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 | #T commandline: ['-j4']
3 |
4 | $(shell touch foo.in)
5 |
6 | all: foo.in foo.out missing
7 | @echo TEST-PASS
8 |
9 | %.out: %.in
10 | cp $< $@
11 |
--------------------------------------------------------------------------------
/tests/diamond-deps.mk:
--------------------------------------------------------------------------------
1 | # If the dependency graph includes a diamond dependency, we should only remake
2 | # once!
3 |
4 | all: depA depB
5 | cat testfile
6 | test `cat testfile` = "data";
7 | @echo TEST-PASS
8 |
9 | depA: testfile
10 | depB: testfile
11 |
12 | testfile:
13 | printf "data" >>$@
14 |
--------------------------------------------------------------------------------
/tests/dotslash-dir.mk:
--------------------------------------------------------------------------------
1 | #T grep-for: "dotslash-built"
2 | .PHONY: $(dir foo)
3 |
4 | all: $(dir foo)
5 | @echo TEST-PASS
6 |
7 | $(dir foo):
8 | @echo dotslash-built
9 |
--------------------------------------------------------------------------------
/tests/dotslash-parse.mk:
--------------------------------------------------------------------------------
1 | ./:
2 |
3 | # This is merely a test to see that pymake doesn't choke on parsing ./
4 | $(info TEST-PASS)
5 |
--------------------------------------------------------------------------------
/tests/dotslash-phony.mk:
--------------------------------------------------------------------------------
1 | .PHONY: ./
2 | ./:
3 | @echo TEST-PASS
4 |
--------------------------------------------------------------------------------
/tests/dotslash.mk:
--------------------------------------------------------------------------------
1 | $(shell touch foo.in)
2 |
3 | all: foo.out
4 | test "$(wildcard ./*.in)" = "./foo.in"
5 | @echo TEST-PASS
6 |
7 | ./%.out: %.in
8 | test "$@" = "foo.out"
9 | cp $< $@
10 |
--------------------------------------------------------------------------------
/tests/doublecolon-exists.mk:
--------------------------------------------------------------------------------
1 | $(shell touch foo.testfile1 foo.testfile2)
2 |
3 | # when a rule has commands and no prerequisites, should it be executed?
4 | # double-colon: yes
5 | # single-colon: no
6 |
7 | all: foo.testfile1 foo.testfile2
8 | test "$$(cat foo.testfile1)" = ""
9 | test "$$(cat foo.testfile2)" = "remade:foo.testfile2"
10 | @echo TEST-PASS
11 |
12 | foo.testfile1:
13 | @echo TEST-FAIL
14 |
15 | foo.testfile2::
16 | printf "remade:$@"> $@
17 |
--------------------------------------------------------------------------------
/tests/doublecolon-priordeps.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-j3']
2 |
3 | # All *prior* dependencies of a doublecolon rule must be satisfied before
4 | # subsequent commands are run.
5 |
6 | all:: target1
7 |
8 | all:: target2
9 | test -f target1
10 | @echo TEST-PASS
11 |
12 | target1:
13 | touch starting-$@
14 | sleep 1
15 | touch $@
16 |
17 | target2:
18 | sleep 0.1
19 | test -f starting-target1
20 |
--------------------------------------------------------------------------------
/tests/doublecolon-remake.mk:
--------------------------------------------------------------------------------
1 | $(shell touch somefile)
2 |
3 | all:: somefile
4 | @echo TEST-PASS
5 |
--------------------------------------------------------------------------------
/tests/dynamic-var.mk:
--------------------------------------------------------------------------------
1 | # The *name* of variables can be constructed dynamically.
2 |
3 | VARNAME = FOOBAR
4 |
5 | $(VARNAME) = foovalue
6 | $(VARNAME)2 = foo2value
7 |
8 | $(VARNAME:%BAR=%BAM) = foobam
9 |
10 | all:
11 | test "$(FOOBAR)" = "foovalue"
12 | test "$(flavor FOOBAZ)" = "undefined"
13 | test "$(FOOBAR2)" = "bazvalue"
14 | test "$(FOOBAM)" = "foobam"
15 | @echo TEST-PASS
16 |
17 | VARNAME = FOOBAZ
18 | FOOBAR2 = bazvalue
19 |
--------------------------------------------------------------------------------
/tests/empty-arg.mk:
--------------------------------------------------------------------------------
1 | all:
2 | @ sh -c 'if [ $$# = 3 ] ; then echo TEST-PASS; else echo TEST-FAIL; fi' -- a "" b
3 |
--------------------------------------------------------------------------------
/tests/empty-command-semicolon.mk:
--------------------------------------------------------------------------------
1 | all:
2 | @echo TEST-PASS
3 |
4 | foo: ;
5 |
6 |
--------------------------------------------------------------------------------
/tests/empty-rule.mk:
--------------------------------------------------------------------------------
1 | all: obj
2 | $(MAKE) -f $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) obj
3 |
4 | obj: header
5 | if [ -e obj ]; then echo TEST-FAIL; fi;
6 | touch obj
7 |
8 | header: cpp
9 |
10 | cpp:
11 | touch header
12 | touch cpp
13 |
--------------------------------------------------------------------------------
/tests/empty-with-deps.mk:
--------------------------------------------------------------------------------
1 | default.test: default.c
2 |
3 | default.c:
4 | @echo TEST-PASS
5 |
--------------------------------------------------------------------------------
/tests/env-var-append.mk:
--------------------------------------------------------------------------------
1 | #T environment: {'FOO': 'TEST'}
2 |
3 | FOO += $(BAR)
4 | BAR := PASS
5 |
6 | all:
7 | @echo $(subst $(NULL) ,-,$(FOO))
8 |
--------------------------------------------------------------------------------
/tests/env-var-append2.mk:
--------------------------------------------------------------------------------
1 | #T environment: {'FOO': '$(BAZ)'}
2 |
3 | FOO += $(BAR)
4 | BAR := PASS
5 | BAZ := TEST
6 |
7 | all:
8 | @echo $(subst $(NULL) ,-,$(FOO))
9 |
--------------------------------------------------------------------------------
/tests/eof-continuation.mk:
--------------------------------------------------------------------------------
1 | all:
2 | test '$(TESTVAR)' = 'testval\'
3 | @echo TEST-PASS
4 |
5 | TESTVAR = testval\
--------------------------------------------------------------------------------
/tests/escape-chars.mk:
--------------------------------------------------------------------------------
1 | space = $(NULL) $(NULL)
2 | hello$(space)world$(space) = hellovalue
3 |
4 | A = aval
5 |
6 | VAR = value1\\
7 | VARAWFUL = value1\\#comment
8 | VAR2 = value2
9 | VAR3 = test\$A
10 | VAR4 = value4\\value5
11 |
12 | VAR5 = value1\\ \ \
13 | value2
14 |
15 | EPERCENT = \%
16 |
17 | all:
18 | test "$(hello world )" = "hellovalue"
19 | test "$(VAR)" = "value1\\"
20 | test '$(VARAWFUL)' = 'value1\'
21 | test "$(VAR2)" = "value2"
22 | test "$(VAR3)" = "test\aval"
23 | test "$(VAR4)" = "value4\\value5"
24 | test "$(VAR5)" = "value1\\ \ value2"
25 | test "$(EPERCENT)" = "\%"
26 | @echo TEST-PASS
27 |
--------------------------------------------------------------------------------
/tests/escaped-continuation.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | all:
4 | echo "Hello" \\
5 | test "world" = "not!"
6 | @echo TEST-PASS
7 |
--------------------------------------------------------------------------------
/tests/eval-duringexecute.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | # Once parsing is finished, recursive expansion in commands are not allowed to create any new rules (it may only set variables)
4 |
5 | define MORERULE
6 | all:
7 | @echo TEST-FAIL
8 | endef
9 |
10 | all:
11 | $(eval $(MORERULE))
12 | @echo done
13 |
--------------------------------------------------------------------------------
/tests/eval.mk:
--------------------------------------------------------------------------------
1 | TESTVAR = val1
2 |
3 | $(eval TESTVAR = val2)
4 |
5 | all:
6 | test "$(TESTVAR)" = "val2"
7 | @echo TEST-PASS
8 |
--------------------------------------------------------------------------------
/tests/exit-code.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | all:
4 | exit 1
5 | @echo TEST-PASS
6 |
--------------------------------------------------------------------------------
/tests/file-functions-symlinks.mk:
--------------------------------------------------------------------------------
1 | #T returncode-on: {'win32': 2}
2 | $(shell \
3 | touch test.file; \
4 | ln -s test.file test.symlink; \
5 | ln -s test.missing missing.symlink; \
6 | touch .testhidden; \
7 | mkdir foo; \
8 | touch foo/testfile; \
9 | ln -s foo symdir; \
10 | )
11 |
12 | all:
13 | test "$(abspath test.file test.symlink)" = "$(CURDIR)/test.file $(CURDIR)/test.symlink"
14 | test "$(realpath test.file test.symlink)" = "$(CURDIR)/test.file $(CURDIR)/test.file"
15 | test "$(sort $(wildcard *))" = "foo symdir test.file test.symlink"
16 | test "$(sort $(wildcard .*))" = ". .. .testhidden"
17 | test "$(sort $(wildcard test*))" = "test.file test.symlink"
18 | test "$(sort $(wildcard foo/*))" = "foo/testfile"
19 | test "$(sort $(wildcard ./*))" = "./foo ./symdir ./test.file ./test.symlink"
20 | test "$(sort $(wildcard f?o/*))" = "foo/testfile"
21 | test "$(sort $(wildcard */*))" = "foo/testfile symdir/testfile"
22 | @echo TEST-PASS
23 |
--------------------------------------------------------------------------------
/tests/file-functions.mk:
--------------------------------------------------------------------------------
1 | $(shell \
2 | touch test.file; \
3 | touch .testhidden; \
4 | mkdir foo; \
5 | touch foo/testfile; \
6 | )
7 |
8 | all:
9 | test "$(abspath test.file)" = "$(CURDIR)/test.file"
10 | test "$(realpath test.file)" = "$(CURDIR)/test.file"
11 | test "$(sort $(wildcard *))" = "foo test.file"
12 | # commented out because GNU make matches . and .. while python doesn't, and I don't
13 | # care enough
14 | # test "$(sort $(wildcard .*))" = ". .. .testhidden"
15 | test "$(sort $(wildcard test*))" = "test.file"
16 | test "$(sort $(wildcard foo/*))" = "foo/testfile"
17 | test "$(sort $(wildcard ./*))" = "./foo ./test.file"
18 | test "$(sort $(wildcard f?o/*))" = "foo/testfile"
19 | @echo TEST-PASS
20 |
--------------------------------------------------------------------------------
/tests/foreach-local-variable.mk:
--------------------------------------------------------------------------------
1 | # This test ensures that a local variable in a $(foreach) is bound to
2 | # the local value, not a global value.
3 | i := dummy
4 |
5 | all:
6 | test "$(foreach i,foo bar,found:$(i))" = "found:foo found:bar"
7 | test "$(i)" = "dummy"
8 | @echo TEST-PASS
9 |
--------------------------------------------------------------------------------
/tests/formattingtests.py:
--------------------------------------------------------------------------------
1 | # This file contains test code for the formatting of parsed statements back to
2 | # make file "source." It essentially verifies to to_source() functions
3 | # scattered across the tree.
4 |
5 | import glob
6 | import logging
7 | import os.path
8 | import unittest
9 |
10 | from pymake.data import Expansion
11 | from pymake.data import StringExpansion
12 | from pymake.functions import BasenameFunction
13 | from pymake.functions import SubstitutionRef
14 | from pymake.functions import VariableRef
15 | from pymake.functions import WordlistFunction
16 | from pymake.parserdata import Include
17 | from pymake.parserdata import SetVariable
18 | from pymake.parser import parsestring
19 | from pymake.parser import SyntaxError
20 |
21 | class TestBase(unittest.TestCase):
22 | pass
23 |
24 | class VariableRefTest(TestBase):
25 | def test_string_name(self):
26 | e = StringExpansion('foo', None)
27 | v = VariableRef(None, e)
28 |
29 | self.assertEqual(v.to_source(), '$(foo)')
30 |
31 | def test_special_variable(self):
32 | e = StringExpansion('<', None)
33 | v = VariableRef(None, e)
34 |
35 | self.assertEqual(v.to_source(), '$<')
36 |
37 | def test_expansion_simple(self):
38 | e = Expansion()
39 | e.appendstr('foo')
40 | e.appendstr('bar')
41 |
42 | v = VariableRef(None, e)
43 |
44 | self.assertEqual(v.to_source(), '$(foobar)')
45 |
46 | class StandardFunctionTest(TestBase):
47 | def test_basename(self):
48 | e1 = StringExpansion('foo', None)
49 | v = VariableRef(None, e1)
50 | e2 = Expansion(None)
51 | e2.appendfunc(v)
52 |
53 | b = BasenameFunction(None)
54 | b.append(e2)
55 |
56 | self.assertEqual(b.to_source(), '$(basename $(foo))')
57 |
58 | def test_wordlist(self):
59 | e1 = StringExpansion('foo', None)
60 | e2 = StringExpansion('bar ', None)
61 | e3 = StringExpansion(' baz', None)
62 |
63 | w = WordlistFunction(None)
64 | w.append(e1)
65 | w.append(e2)
66 | w.append(e3)
67 |
68 | self.assertEqual(w.to_source(), '$(wordlist foo,bar , baz)')
69 |
70 | def test_curly_brackets(self):
71 | e1 = Expansion(None)
72 | e1.appendstr('foo')
73 |
74 | e2 = Expansion(None)
75 | e2.appendstr('foo ( bar')
76 |
77 | f = WordlistFunction(None)
78 | f.append(e1)
79 | f.append(e2)
80 |
81 | self.assertEqual(f.to_source(), '${wordlist foo,foo ( bar}')
82 |
83 | class StringExpansionTest(TestBase):
84 | def test_simple(self):
85 | e = StringExpansion('foobar', None)
86 | self.assertEqual(e.to_source(), 'foobar')
87 |
88 | e = StringExpansion('$var', None)
89 | self.assertEqual(e.to_source(), '$var')
90 |
91 | def test_escaping(self):
92 | e = StringExpansion('$var', None)
93 | self.assertEqual(e.to_source(escape_variables=True), '$$var')
94 |
95 | e = StringExpansion('this is # not a comment', None)
96 | self.assertEqual(e.to_source(escape_comments=True),
97 | 'this is \# not a comment')
98 |
99 | def test_empty(self):
100 | e = StringExpansion('', None)
101 | self.assertEqual(e.to_source(), '')
102 |
103 | e = StringExpansion(' ', None)
104 | self.assertEqual(e.to_source(), ' ')
105 |
106 | class ExpansionTest(TestBase):
107 | def test_single_string(self):
108 | e = Expansion()
109 | e.appendstr('foo')
110 |
111 | self.assertEqual(e.to_source(), 'foo')
112 |
113 | def test_multiple_strings(self):
114 | e = Expansion()
115 | e.appendstr('hello')
116 | e.appendstr('world')
117 |
118 | self.assertEqual(e.to_source(), 'helloworld')
119 |
120 | def test_string_escape(self):
121 | e = Expansion()
122 | e.appendstr('$var')
123 | self.assertEqual(e.to_source(), '$var')
124 | self.assertEqual(e.to_source(escape_variables=True), '$$var')
125 |
126 | e = Expansion()
127 | e.appendstr('foo')
128 | e.appendstr(' $bar')
129 | self.assertEqual(e.to_source(escape_variables=True), 'foo $$bar')
130 |
131 | class SubstitutionRefTest(TestBase):
132 | def test_simple(self):
133 | name = StringExpansion('foo', None)
134 | c = StringExpansion('%.c', None)
135 | o = StringExpansion('%.o', None)
136 | s = SubstitutionRef(None, name, c, o)
137 |
138 | self.assertEqual(s.to_source(), '$(foo:%.c=%.o)')
139 |
140 | class SetVariableTest(TestBase):
141 | def test_simple(self):
142 | v = SetVariable(StringExpansion('foo', None), '=', 'bar', None, None)
143 | self.assertEqual(v.to_source(), 'foo = bar')
144 |
145 | def test_multiline(self):
146 | s = 'hello\nworld'
147 | foo = StringExpansion('FOO', None)
148 |
149 | v = SetVariable(foo, '=', s, None, None)
150 |
151 | self.assertEqual(v.to_source(), 'define FOO\nhello\nworld\nendef')
152 |
153 | def test_multiline_immediate(self):
154 | source = 'define FOO :=\nhello\nworld\nendef'
155 |
156 | statements = parsestring(source, 'foo.mk')
157 | self.assertEqual(statements.to_source(), source)
158 |
159 | def test_target_specific(self):
160 | foo = StringExpansion('FOO', None)
161 | bar = StringExpansion('BAR', None)
162 |
163 | v = SetVariable(foo, '+=', 'value', None, bar)
164 |
165 | self.assertEqual(v.to_source(), 'BAR: FOO += value')
166 |
167 | class IncludeTest(TestBase):
168 | def test_include(self):
169 | e = StringExpansion('rules.mk', None)
170 | i = Include(e, True, False)
171 | self.assertEqual(i.to_source(), 'include rules.mk')
172 |
173 | i = Include(e, False, False)
174 | self.assertEqual(i.to_source(), '-include rules.mk')
175 |
176 | class IfdefTest(TestBase):
177 | def test_simple(self):
178 | source = 'ifdef FOO\nbar := $(value)\nendif'
179 |
180 | statements = parsestring(source, 'foo.mk')
181 | self.assertEqual(statements[0].to_source(), source)
182 |
183 | def test_nested(self):
184 | source = 'ifdef FOO\nifdef BAR\nhello = world\nendif\nendif'
185 |
186 | statements = parsestring(source, 'foo.mk')
187 | self.assertEqual(statements[0].to_source(), source)
188 |
189 | def test_negation(self):
190 | source = 'ifndef FOO\nbar += value\nendif'
191 |
192 | statements = parsestring(source, 'foo.mk')
193 | self.assertEqual(statements[0].to_source(), source)
194 |
195 | class IfeqTest(TestBase):
196 | def test_simple(self):
197 | source = 'ifeq ($(foo),bar)\nhello = $(world)\nendif'
198 |
199 | statements = parsestring(source, 'foo.mk')
200 | self.assertEqual(statements[0].to_source(), source)
201 |
202 | def test_negation(self):
203 | source = 'ifneq (foo,bar)\nhello = world\nendif'
204 |
205 | statements = parsestring(source, 'foo.mk')
206 | self.assertEqual(statements.to_source(), source)
207 |
208 | class ConditionBlocksTest(TestBase):
209 | def test_mixed_conditions(self):
210 | source = 'ifdef FOO\nifeq ($(FOO),bar)\nvar += $(value)\nendif\nendif'
211 |
212 | statements = parsestring(source, 'foo.mk')
213 | self.assertEqual(statements.to_source(), source)
214 |
215 | def test_extra_statements(self):
216 | source = 'ifdef FOO\nF := 1\nifdef BAR\nB += 1\nendif\nC = 1\nendif'
217 |
218 | statements = parsestring(source, 'foo.mk')
219 | self.assertEqual(statements.to_source(), source)
220 |
221 | def test_whitespace_preservation(self):
222 | source = "ifeq ' x' 'x '\n$(error stripping)\nendif"
223 |
224 | statements = parsestring(source, 'foo.mk')
225 | self.assertEqual(statements.to_source(), source)
226 |
227 | source = 'ifneq (x , x)\n$(error stripping)\nendif'
228 | statements = parsestring(source, 'foo.mk')
229 | self.assertEqual(statements.to_source(),
230 | 'ifneq (x,x)\n$(error stripping)\nendif')
231 |
232 | class MakefileCorupusTest(TestBase):
233 | """Runs the make files from the pymake corpus through the formatter.
234 |
235 | All the above tests are child's play compared to this.
236 | """
237 |
238 | # Our reformatting isn't perfect. We ignore files with known failures until
239 | # we make them work.
240 | # TODO Address these formatting corner cases.
241 | _IGNORE_FILES = [
242 | # We are thrown off by backslashes at end of lines.
243 | 'comment-parsing.mk',
244 | 'escape-chars.mk',
245 | 'include-notfound.mk',
246 | ]
247 |
248 | def _get_test_files(self):
249 | ourdir = os.path.dirname(os.path.abspath(__file__))
250 |
251 | for makefile in glob.glob(os.path.join(ourdir, '*.mk')):
252 | if os.path.basename(makefile) in self._IGNORE_FILES:
253 | continue
254 |
255 | source = None
256 | with open(makefile, 'rU') as fh:
257 | source = fh.read()
258 |
259 | try:
260 | yield (makefile, source, parsestring(source, makefile))
261 | except SyntaxError:
262 | continue
263 |
264 | def test_reparse_consistency(self):
265 | for filename, source, statements in self._get_test_files():
266 | reformatted = statements.to_source()
267 |
268 | # We should be able to parse the reformatted source fine.
269 | new_statements = parsestring(reformatted, filename)
270 |
271 | # If we do the formatting again, the representation shouldn't
272 | # change. i.e. the only lossy change should be the original
273 | # (whitespace and some semantics aren't preserved).
274 | reformatted_again = new_statements.to_source()
275 | self.assertEqual(reformatted, reformatted_again,
276 | '%s has lossless reformat.' % filename)
277 |
278 | self.assertEqual(len(statements), len(new_statements))
279 |
280 | for i in range(0, len(statements)):
281 | original = statements[i]
282 | formatted = new_statements[i]
283 |
284 | self.assertEqual(original, formatted, '%s %d: %s != %s' % (filename,
285 | i, original, formatted))
286 |
287 | if __name__ == '__main__':
288 | logging.basicConfig(level=logging.DEBUG)
289 | unittest.main()
290 |
--------------------------------------------------------------------------------
/tests/func-refs.mk:
--------------------------------------------------------------------------------
1 | unknown var = uval
2 |
3 | all:
4 | test "$(subst a,b,value)" = "vblue"
5 | test "${subst a,b,va)lue}" = "vb)lue"
6 | test "$(subst /,\,ab/c)" = "ab\c"
7 | test '$(subst a,b,\\#)' = '\\#'
8 | test "$( subst a,b,value)" = ""
9 | test "$(Subst a,b,value)" = ""
10 | test "$(unknown var)" = "uval"
11 | @echo TEST-PASS
12 |
--------------------------------------------------------------------------------
/tests/functions.mk:
--------------------------------------------------------------------------------
1 | all:
2 | test "$(subst e,EE,hello)" = "hEEllo"
3 | test "$(strip $(NULL) test data )" = "test data"
4 | test "$(findstring hell,hello)" = "hell"
5 | test "$(findstring heaven,hello)" = ""
6 | test "$(filter foo/%.c b%,foo/a.c b.c foo/a.o)" = "foo/a.c b.c"
7 | test "$(filter foo,foo bar)" = "foo"
8 | test "$(filter-out foo/%.c b%,foo/a.c b.c foo/a.o)" = "foo/a.o"
9 | test "$(filter-out %.c,foo,bar.c foo,bar.o)" = "foo,bar.o"
10 | test "$(sort .go a b aa A c cc)" = ".go A a aa b c cc"
11 | test "$(word 1, hello )" = "hello"
12 | test "$(word 2, hello )" = ""
13 | test "$(wordlist 1, 2, foo bar baz )" = "foo bar"
14 | test "$(words 1 2 3)" = "3"
15 | test "$(words )" = "0"
16 | test "$(firstword $(NULL) foo bar baz)" = "foo"
17 | test "$(firstword )" = ""
18 | test "$(dir foo.c path/foo.o dir/dir2/)" = "./ path/ dir/dir2/"
19 | test "$(notdir foo.c path/foo.o dir/dir2/)" = "foo.c foo.o "
20 | test "$(suffix src/foo.c dir/my.dir/foo foo.o)" = ".c .o"
21 | test "$(basename src/foo.c dir/my.dir/foo foo.c .c)" = "src/foo dir/my.dir/foo foo "
22 | test "$(addprefix src/,foo bar.c dir/foo)" = "src/foo src/bar.c src/dir/foo"
23 | test "$(addsuffix .c,foo dir/bar)" = "foo.c dir/bar.c"
24 | test "$(join a b c, 1 2 3)" = "a1 b2 c3"
25 | test "$(join a b, 1 2 3)" = "a1 b2 3"
26 | test "$(join a b c, 1 2)" = "a1 b2 c"
27 | test "$(if $(NULL) ,yes)" = ""
28 | test "$(if 1,yes,no)" = "yes"
29 | test "$(if ,yes,no )" = "no "
30 | test "$(if ,$(error Short-circuit problem))" = ""
31 | test "$(or $(NULL),1)" = "1"
32 | test "$(or $(NULL),2,$(warning TEST-FAIL bad or short-circuit))" = "2"
33 | test "$(and ,$(warning TEST-FAIL bad and short-circuit))" = ""
34 | test "$(and 1,2)" = "2"
35 | test "$(foreach i,foo bar,found:$(i))" = "found:foo found:bar"
36 | @echo TEST-PASS
37 |
--------------------------------------------------------------------------------
/tests/functiontests.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import pymake.data
4 | import pymake.functions
5 |
6 | class VariableRefTest(unittest.TestCase):
7 | def test_get_expansions(self):
8 | e = pymake.data.StringExpansion('FOO', None)
9 | f = pymake.functions.VariableRef(None, e)
10 |
11 | exps = list(f.expansions())
12 | self.assertEqual(len(exps), 1)
13 |
14 | class GetExpansionsTest(unittest.TestCase):
15 | def test_get_arguments(self):
16 | f = pymake.functions.SubstFunction(None)
17 |
18 | e1 = pymake.data.StringExpansion('FOO', None)
19 | e2 = pymake.data.StringExpansion('BAR', None)
20 | e3 = pymake.data.StringExpansion('BAZ', None)
21 |
22 | f.append(e1)
23 | f.append(e2)
24 | f.append(e3)
25 |
26 | exps = list(f.expansions())
27 | self.assertEqual(len(exps), 3)
28 |
29 | def test_descend(self):
30 | f = pymake.functions.StripFunction(None)
31 |
32 | e = pymake.data.Expansion(None)
33 |
34 | e1 = pymake.data.StringExpansion('FOO', None)
35 | f1 = pymake.functions.VariableRef(None, e1)
36 | e.appendfunc(f1)
37 |
38 | f2 = pymake.functions.WildcardFunction(None)
39 | e2 = pymake.data.StringExpansion('foo/*', None)
40 | f2.append(e2)
41 | e.appendfunc(f2)
42 |
43 | f.append(e)
44 |
45 | exps = list(f.expansions())
46 | self.assertEqual(len(exps), 1)
47 |
48 | exps = list(f.expansions(True))
49 | self.assertEqual(len(exps), 3)
50 |
51 | self.assertFalse(f.is_filesystem_dependent)
52 |
53 | if __name__ == '__main__':
54 | unittest.main()
55 |
--------------------------------------------------------------------------------
/tests/glob1.inc:
--------------------------------------------------------------------------------
1 | A=ok
2 |
--------------------------------------------------------------------------------
/tests/glob2.inc:
--------------------------------------------------------------------------------
1 | B=ok
--------------------------------------------------------------------------------
/tests/if-syntaxerr.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | ifeq ($(FOO,VAR))
4 | all:
5 | @echo TEST_FAIL
6 | endif
7 |
--------------------------------------------------------------------------------
/tests/ifdefs-nesting.mk:
--------------------------------------------------------------------------------
1 | ifdef RANDOM
2 | ifeq (,$(error Not evaluated!))
3 | endif
4 | endif
5 |
6 | ifdef RANDOM
7 | ifeq (,)
8 | else ifeq (,$(error Not evaluated!))
9 | endif
10 | endif
11 |
12 | all:
13 | @echo TEST-PASS
14 |
--------------------------------------------------------------------------------
/tests/ifdefs.mk:
--------------------------------------------------------------------------------
1 | ifdef FOO
2 | $(error FOO is not defined!)
3 | endif
4 |
5 | FOO = foo
6 | FOOFOUND = false
7 | BARFOUND = false
8 | BAZFOUND = false
9 |
10 | ifdef FOO
11 | FOOFOUND = true
12 | else ifdef BAR
13 | BARFOUND = true
14 | else
15 | BAZFOUND = true
16 | endif
17 |
18 | BAR2 = bar2
19 | FOO2FOUND = false
20 | BAR2FOUND = false
21 | BAZ2FOUND = false
22 |
23 | ifdef FOO2
24 | FOO2FOUND = true
25 | else ifdef BAR2
26 | BAR2FOUND = true
27 | else
28 | BAZ2FOUND = true
29 | endif
30 |
31 | FOO3FOUND = false
32 | BAR3FOUND = false
33 | BAZ3FOUND = false
34 |
35 | ifdef FOO3
36 | FOO3FOUND = true
37 | else ifdef BAR3
38 | BAR3FOUND = true
39 | else
40 | BAZ3FOUND = true
41 | endif
42 |
43 | ifdef RANDOM
44 | CONTINUATION = \
45 | else \
46 | endif
47 | endif
48 |
49 | ifndef ASDFJK
50 | else
51 | $(error ASFDJK was not set)
52 | endif
53 |
54 | TESTSET =
55 |
56 | ifdef TESTSET
57 | $(error TESTSET was not set)
58 | endif
59 |
60 | TESTEMPTY = $(NULL)
61 | ifndef TESTEMPTY
62 | $(error TEST-FAIL TESTEMPTY was probably expanded!)
63 | endif
64 |
65 | # ifneq ( a,a)
66 | # $(error Arguments to ifeq should be stripped before evaluation)
67 | # endif
68 |
69 | XSPACE = x # trick
70 |
71 | ifneq ($(NULL),$(NULL))
72 | $(error TEST-FAIL ifneq)
73 | endif
74 |
75 | ifneq (x , x)
76 | $(error argument-stripping1)
77 | endif
78 |
79 | ifeq ( x,x )
80 | $(error argument-stripping2)
81 | endif
82 |
83 | ifneq ($(XSPACE), x )
84 | $(error argument-stripping3)
85 | endif
86 |
87 | ifeq 'x ' ' x'
88 | $(error TEST-FAIL argument-stripping4)
89 | endif
90 |
91 | all:
92 | test $(FOOFOUND) = true # FOOFOUND
93 | test $(BARFOUND) = false # BARFOUND
94 | test $(BAZFOUND) = false # BAZFOUND
95 | test $(FOO2FOUND) = false # FOO2FOUND
96 | test $(BAR2FOUND) = true # BAR2FOUND
97 | test $(BAZ2FOUND) = false # BAZ2FOUND
98 | test $(FOO3FOUND) = false # FOO3FOUND
99 | test $(BAR3FOUND) = false # BAR3FOUND
100 | test $(BAZ3FOUND) = true # BAZ3FOUND
101 | ifneq ($(FOO),foo)
102 | echo TEST-FAIL 'FOO neq foo: "$(FOO)"'
103 | endif
104 | ifneq ($(FOO), foo) # Whitespace after the comma is stripped
105 | echo TEST-FAIL 'FOO plus whitespace'
106 | endif
107 | ifeq ($(FOO), foo ) # But not trailing whitespace
108 | echo TEST-FAIL 'FOO plus trailing whitespace'
109 | endif
110 | ifeq ( $(FOO),foo) # Not whitespace after the paren
111 | echo TEST-FAIL 'FOO with leading whitespace'
112 | endif
113 | ifeq ($(FOO),$(NULL) foo) # Nor whitespace after expansion
114 | echo TEST-FAIL 'FOO with embedded ws'
115 | endif
116 | ifeq ($(BAR2),bar)
117 | echo TEST-FAIL 'BAR2 eq bar'
118 | endif
119 | ifeq '$(BAR3FOUND)' 'false'
120 | echo BAR3FOUND is ok
121 | else
122 | echo TEST-FAIL BAR3FOUND is not ok
123 | endif
124 | ifndef FOO
125 | echo TEST-FAIL "foo not defined?"
126 | endif
127 | @echo TEST-PASS
128 |
--------------------------------------------------------------------------------
/tests/ignore-error.mk:
--------------------------------------------------------------------------------
1 | all:
2 | -rm foo
3 | +-rm bar
4 | -+rm baz
5 | @-rm bah
6 | -@rm humbug
7 | +-@rm sincere
8 | +@-rm flattery
9 | @+-rm will
10 | @-+rm not
11 | -+@rm save
12 | -@+rm you
13 | @echo TEST-PASS
14 |
--------------------------------------------------------------------------------
/tests/implicit-chain.mk:
--------------------------------------------------------------------------------
1 | all: test.prog
2 | test "$$(cat $<)" = "Program: Object: Source: test.source"
3 | @echo TEST-PASS
4 |
5 | %.prog: %.object
6 | printf "Program: %s" "$$(cat $<)" > $@
7 |
8 | %.object: %.source
9 | printf "Object: %s" "$$(cat $<)" > $@
10 |
11 | %.source:
12 | printf "Source: %s" $@ > $@
13 |
--------------------------------------------------------------------------------
/tests/implicit-dir.mk:
--------------------------------------------------------------------------------
1 | # Implicit rules have special instructions to deal with directories, so that a pattern rule which doesn't directly apply
2 | # may still be used.
3 |
4 | all: dir/host_test.otest
5 |
6 | host_%.otest: %.osource extra.file
7 | @echo making $@ from $<
8 |
9 | test.osource:
10 | @echo TEST-FAIL should have made dir/test.osource
11 |
12 | dir/test.osource:
13 | @echo TEST-PASS made the correct dependency
14 |
15 | extra.file:
16 | @echo building $@
17 |
--------------------------------------------------------------------------------
/tests/implicit-terminal.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | # the %.object rule is "terminal". This means that additional implicit rules cannot be chained to it.
4 |
5 | all: test.prog
6 | test "$$(cat $<)" = "Program: Object: Source: test.source"
7 | @echo TEST-FAIL
8 |
9 | %.prog: %.object
10 | printf "Program: %s" "$$(cat $<)" > $@
11 |
12 | %.object:: %.source
13 | printf "Object: %s" "$$(cat $<)" > $@
14 |
15 | %.source:
16 | printf "Source: %s" $@ > $@
17 |
--------------------------------------------------------------------------------
/tests/implicitsubdir.mk:
--------------------------------------------------------------------------------
1 | $(shell \
2 | mkdir foo; \
3 | touch test.in \
4 | )
5 |
6 | all: foo/test.out
7 | @echo TEST-PASS
8 |
9 | foo/%.out: %.in
10 | cp $< $@
11 |
12 |
13 |
--------------------------------------------------------------------------------
/tests/include-dynamic.mk:
--------------------------------------------------------------------------------
1 | $(shell \
2 | if ! test -f include-dynamic.inc; then \
3 | echo "TESTVAR = oldval" > include-dynamic.inc; \
4 | sleep 2; \
5 | echo "TESTVAR = newval" > include-dynamic.inc.in; \
6 | fi \
7 | )
8 |
9 | # before running the 'all' rule, we should be rebuilding include-dynamic.inc,
10 | # because there is a rule to do so
11 |
12 | all:
13 | test $(TESTVAR) = newval
14 | test "$(MAKE_RESTARTS)" = 1
15 | @echo TEST-PASS
16 |
17 | include-dynamic.inc: include-dynamic.inc.in
18 | test "$(MAKE_RESTARTS)" = ""
19 | cp $< $@
20 |
21 | include include-dynamic.inc
22 |
--------------------------------------------------------------------------------
/tests/include-file.inc:
--------------------------------------------------------------------------------
1 | INCLUDED = yes
2 |
--------------------------------------------------------------------------------
/tests/include-glob.mk:
--------------------------------------------------------------------------------
1 | include $(TESTPATH)/glob*.inc
2 |
3 | ifeq ($A_$B,ok_ok)
4 | all:
5 | @echo TEST-PASS
6 | else
7 | all:
8 | @echo TEST-FAIL
9 | endif
10 |
--------------------------------------------------------------------------------
/tests/include-missing.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | # If an include file isn't present and doesn't have a rule to remake it, make
4 | # should fail.
5 |
6 | include notfound.mk
7 |
8 | all:
9 | @echo TEST-FAIL
10 |
--------------------------------------------------------------------------------
/tests/include-notfound.mk:
--------------------------------------------------------------------------------
1 | ifdef __WIN32__
2 | PS:=\\#
3 | else
4 | PS:=/
5 | endif
6 |
7 | ifneq ($(strip $(MAKEFILE_LIST)),$(NATIVE_TESTPATH)$(PS)include-notfound.mk)
8 | $(error MAKEFILE_LIST incorrect: '$(MAKEFILE_LIST)' (expected '$(NATIVE_TESTPATH)$(PS)include-notfound.mk'))
9 | endif
10 |
11 | -include notfound.inc-dummy
12 |
13 | ifneq ($(strip $(MAKEFILE_LIST)),$(NATIVE_TESTPATH)$(PS)include-notfound.mk)
14 | $(error MAKEFILE_LIST incorrect: '$(MAKEFILE_LIST)' (expected '$(NATIVE_TESTPATH)$(PS)include-notfound.mk'))
15 | endif
16 |
17 | all:
18 | @echo TEST-PASS
19 |
20 |
--------------------------------------------------------------------------------
/tests/include-optional-warning.mk:
--------------------------------------------------------------------------------
1 | -include TEST-FAIL.mk
2 |
3 | all:
4 | @echo TEST-PASS
5 |
--------------------------------------------------------------------------------
/tests/include-regen.mk:
--------------------------------------------------------------------------------
1 | # avoid infinite loops by not remaking makefiles with
2 | # double-colon no-dependency rules
3 | # http://www.gnu.org/software/make/manual/make.html#Remaking-Makefiles
4 | -include notfound.mk
5 |
6 | all:
7 | @echo TEST-PASS
8 |
9 | notfound.mk::
10 | @echo TEST-FAIL
11 |
--------------------------------------------------------------------------------
/tests/include-regen2.mk:
--------------------------------------------------------------------------------
1 | # make should make makefiles that it has rules for if they are
2 | # included
3 | include test.mk
4 |
5 | all:
6 | test "$(X)" = "1"
7 | @echo "TEST-PASS"
8 |
9 | test.mk:
10 | @echo "X = 1" > $@
11 |
--------------------------------------------------------------------------------
/tests/include-regen3.mk:
--------------------------------------------------------------------------------
1 | # make should make makefiles that it has rules for if they are
2 | # included
3 | -include test.mk
4 |
5 | all:
6 | test "$(X)" = "1"
7 | @echo "TEST-PASS"
8 |
9 | test.mk:
10 | @echo "X = 1" > $@
11 |
--------------------------------------------------------------------------------
/tests/include-required-fails.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | # Required include targets that fail should abort execution.
4 |
5 | include dummy.mk
6 |
7 | all:
8 | @echo TEST-FAIL
9 |
10 | dummy.mk:
11 | @echo "Evaluated dummy.mk"
12 | exit 1
13 |
--------------------------------------------------------------------------------
/tests/include-test.mk:
--------------------------------------------------------------------------------
1 | $(shell echo "INCLUDED2 = yes" >local-include.inc)
2 |
3 | include $(TESTPATH)/include-file.inc local-include.inc
4 |
5 | all:
6 | test "$(INCLUDED)" = "yes"
7 | test "$(INCLUDED2)" = "yes"
8 | @echo TEST-PASS
9 |
--------------------------------------------------------------------------------
/tests/includedeps-norebuild.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 |
3 | $(shell \
4 | touch filemissing; \
5 | sleep 2; \
6 | touch file1; \
7 | )
8 |
9 | all: file1
10 | @echo TEST-PASS
11 |
12 | includedeps $(TESTPATH)/includedeps.deps
13 |
14 | file1:
15 | @echo TEST-FAIL
16 |
--------------------------------------------------------------------------------
/tests/includedeps-sideeffects.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 | #T returncode: 2
3 |
4 | all: file1 filemissing
5 | @echo TEST-PASS
6 |
7 | includedeps $(TESTPATH)/includedeps.deps
8 |
9 | file:
10 | touch $@
11 |
--------------------------------------------------------------------------------
/tests/includedeps-stripdotslash.deps:
--------------------------------------------------------------------------------
1 | ./test: TEST-PASS
2 |
--------------------------------------------------------------------------------
/tests/includedeps-stripdotslash.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 |
3 | test:
4 | @echo $<
5 |
6 | includedeps $(TESTPATH)/includedeps-stripdotslash.deps
7 |
8 | TEST-PASS:
9 |
--------------------------------------------------------------------------------
/tests/includedeps-variables.deps:
--------------------------------------------------------------------------------
1 | $(FILE)1: filemissing
2 |
--------------------------------------------------------------------------------
/tests/includedeps-variables.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 |
3 | FILE = includedeps-variables
4 |
5 | all: $(FILE)1
6 |
7 | includedeps $(TESTPATH)/includedeps-variables.deps
8 |
9 | filemissing:
10 | @echo TEST-PASS
11 |
--------------------------------------------------------------------------------
/tests/includedeps.deps:
--------------------------------------------------------------------------------
1 | file1: filemissing
2 |
--------------------------------------------------------------------------------
/tests/includedeps.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 |
3 | all: file1
4 | @echo TEST-PASS
5 |
6 | includedeps $(TESTPATH)/includedeps.deps
7 |
8 | file1:
9 | touch $@
10 |
--------------------------------------------------------------------------------
/tests/info.mk:
--------------------------------------------------------------------------------
1 | #T grep-for: "info-printed\ninfo-nth"
2 | all:
3 |
4 | INFO = info-printed
5 |
6 | $(info $(INFO))
7 | $(info $(subst second,nth,info-second))
8 | $(info TEST-PASS)
9 |
--------------------------------------------------------------------------------
/tests/justprint-native.mk:
--------------------------------------------------------------------------------
1 | ## $(TOUCH) and $(RM) are native commands in pymake.
2 | ## Test that pymake --just-print just prints them.
3 |
4 | ifndef TOUCH
5 | TOUCH = touch
6 | endif
7 |
8 | all:
9 | $(RM) justprint-native-file1.txt
10 | $(TOUCH) justprint-native-file2.txt
11 | $(MAKE) --just-print -f $(TESTPATH)/justprint-native.mk justprint_target > justprint.log
12 | # make --just-print shouldn't have actually done anything.
13 | test ! -f justprint-native-file1.txt
14 | test -f justprint-native-file2.txt
15 | # but it should have printed each command
16 | grep -q 'touch justprint-native-file1.txt' justprint.log
17 | grep -q 'rm -f justprint-native-file2.txt' justprint.log
18 | grep -q 'this string is "unlikely to appear in the log by chance"' justprint.log
19 | # tidy up
20 | $(RM) justprint-native-file2.txt
21 | @echo TEST-PASS
22 |
23 | justprint_target:
24 | $(TOUCH) justprint-native-file1.txt
25 | $(RM) justprint-native-file2.txt
26 | this string is "unlikely to appear in the log by chance"
27 |
28 | .PHONY: justprint_target
29 |
--------------------------------------------------------------------------------
/tests/justprint.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-n']
2 |
3 | all:
4 | false # without -n, we wouldn't get past this
5 | TEST-PASS # heh
6 |
--------------------------------------------------------------------------------
/tests/keep-going-doublecolon.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-k']
2 | #T returncode: 2
3 | #T grep-for: "TEST-PASS"
4 |
5 | all:: t1
6 | @echo TEST-FAIL "(t1)"
7 |
8 | all:: t2
9 | @echo TEST-PASS
10 |
11 | t1:
12 | @false
13 |
14 | t2:
15 | touch $@
16 |
17 |
--------------------------------------------------------------------------------
/tests/keep-going-parallel.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-k', '-j2']
2 | #T returncode: 2
3 | #T grep-for: "TEST-PASS"
4 |
5 | all: t1 slow1 slow2 slow3 t2
6 |
7 | t2:
8 | @echo TEST-PASS
9 |
10 | slow%:
11 | sleep 1
12 |
--------------------------------------------------------------------------------
/tests/keep-going.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-k']
2 | #T returncode: 2
3 | #T grep-for: "TEST-PASS"
4 |
5 | all: t2 t3
6 |
7 | t1:
8 | @false
9 |
10 | t2: t1
11 | @echo TEST-FAIL
12 |
13 | t3:
14 | @echo TEST-PASS
15 |
--------------------------------------------------------------------------------
/tests/line-continuations.mk:
--------------------------------------------------------------------------------
1 | VAR = val1 \
2 | val2
3 |
4 | VAR2 = val1space\
5 | val2
6 |
7 | VAR3 = val3 \\\
8 | cont3
9 |
10 | all: otarget test.target
11 | test "$(VAR)" = "val1 val2 "
12 | test "$(VAR2)" = "val1space val2"
13 | test '$(VAR3)' = 'val3 \ cont3'
14 | test "hello \
15 | world" = "hello world"
16 | test "hello" = \
17 | "hello"
18 | @echo TEST-PASS
19 |
20 | otarget: ; test "hello\
21 | world" = "helloworld"
22 |
23 | test.target: %.target: ; test "hello\
24 | world" = "helloworld"
25 |
--------------------------------------------------------------------------------
/tests/link-search.mk:
--------------------------------------------------------------------------------
1 | $(shell \
2 | touch libfoo.so \
3 | )
4 |
5 | all: -lfoo
6 | test "$<" = "libfoo.so"
7 | @echo TEST-PASS
8 |
--------------------------------------------------------------------------------
/tests/makeflags.mk:
--------------------------------------------------------------------------------
1 | #T environment: {'MAKEFLAGS': 'OVAR=oval'}
2 |
3 | all:
4 | test "$(OVAR)" = "oval"
5 | test "$$OVAR" = "oval"
6 | @echo TEST-PASS
7 |
8 |
--------------------------------------------------------------------------------
/tests/matchany.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | # we should fail to make foo.ooo from foo.ooo.test
4 | all: foo.ooo
5 | @echo TEST-FAIL
6 |
7 | %.ooo:
8 |
9 | # this match-anything pattern should not apply to %.ooo
10 | %: %.test
11 | cp $< $@
12 |
13 | foo.ooo.test:
14 | touch $@
15 |
--------------------------------------------------------------------------------
/tests/matchany2.mk:
--------------------------------------------------------------------------------
1 | # we should succeed in making foo.ooo from foo.ooo.test
2 | all: foo.ooo
3 | @echo TEST-PASS
4 |
5 | %.ooo: %.ccc
6 | exit 1
7 |
8 | # this match-anything rule is terminal, and therefore applies
9 | %:: %.test
10 | cp $< $@
11 |
12 | foo.ooo.test:
13 | touch $@
14 |
--------------------------------------------------------------------------------
/tests/matchany3.mk:
--------------------------------------------------------------------------------
1 | $(shell \
2 | echo "target" > target.in; \
3 | )
4 |
5 | all: target
6 | test "$$(cat $^)" = "target"
7 | @echo TEST-PASS
8 |
9 | %: %.in
10 | cp $< $@
11 |
--------------------------------------------------------------------------------
/tests/mkdir-fail.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 | all:
3 | mkdir newdir/subdir
4 | test ! -d newdir/subdir
5 | test ! -d newdir
6 | rm -r newdir
7 | @echo TEST-PASS
8 |
--------------------------------------------------------------------------------
/tests/mkdir.mk:
--------------------------------------------------------------------------------
1 | MKDIR ?= mkdir
2 |
3 | all:
4 | $(MKDIR) newdir
5 | test -d newdir
6 | # subdir, parent exists
7 | $(MKDIR) newdir/subdir
8 | test -d newdir/subdir
9 | # -p, existing dir
10 | $(MKDIR) -p newdir
11 | # -p, existing subdir
12 | $(MKDIR) -p newdir/subdir
13 | # multiple subdirs, existing parent
14 | $(MKDIR) newdir/subdir1 newdir/subdir2
15 | test -d newdir/subdir1 -a -d newdir/subdir2
16 | rm -r newdir
17 | # -p, subdir, no existing parent
18 | $(MKDIR) -p newdir/subdir
19 | test -d newdir/subdir
20 | rm -r newdir
21 | # -p, multiple subdirs, no existing parent
22 | $(MKDIR) -p newdir/subdir1 newdir/subdir2
23 | test -d newdir/subdir1 -a -d newdir/subdir2
24 | # -p, multiple existing subdirs
25 | $(MKDIR) -p newdir/subdir1 newdir/subdir2
26 | rm -r newdir
27 | @echo TEST-PASS
28 |
--------------------------------------------------------------------------------
/tests/multiple-rules-prerequisite-merge.mk:
--------------------------------------------------------------------------------
1 | # When a target is defined multiple times, the prerequisites should get
2 | # merged.
3 |
4 | default: foo bar baz
5 |
6 | foo:
7 | test "$<" = "foo.in1"
8 | @echo TEST-PASS
9 |
10 | foo: foo.in1
11 |
12 | bar: bar.in1
13 | test "$<" = "bar.in1"
14 | test "$^" = "bar.in1 bar.in2"
15 | @echo TEST-PASS
16 |
17 | bar: bar.in2
18 |
19 | baz: baz.in2
20 | baz: baz.in1
21 | test "$<" = "baz.in1"
22 | test "$^" = "baz.in1 baz.in2"
23 | @echo TEST-PASS
24 |
25 | foo.in1 bar.in1 bar.in2 baz.in1 baz.in2:
26 |
--------------------------------------------------------------------------------
/tests/native-command-delay-load.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 |
3 | # This test exists to verify that sys.path is adjusted during command
4 | # execution and that delay importing a module will work.
5 |
6 | CMD = %pycmd delayloadfn
7 | PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
8 |
9 | all:
10 | $(CMD)
11 | @echo TEST-PASS
12 |
13 |
--------------------------------------------------------------------------------
/tests/native-command-raise.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 | #T returncode: 2
3 | #T grep-for: "Exception: info-exception"
4 |
5 | CMD = %pycmd asplode_raise
6 | PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
7 |
8 | all:
9 | @$(CMD) info-exception
10 |
--------------------------------------------------------------------------------
/tests/native-command-return-fail1.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 | #T returncode: 2
3 |
4 | CMD = %pycmd asplode_return
5 | PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
6 |
7 | all:
8 | $(CMD) 1
9 |
--------------------------------------------------------------------------------
/tests/native-command-return-fail2.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 | #T returncode: 2
3 |
4 | CMD = %pycmd asplode_return
5 | PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
6 |
7 | all:
8 | $(CMD) not-an-integer
9 |
--------------------------------------------------------------------------------
/tests/native-command-return.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 |
3 | CMD = %pycmd asplode_return
4 | PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
5 |
6 | all:
7 | $(CMD) 0
8 | -$(CMD) 1
9 | $(CMD) None
10 | -$(CMD) not-an-integer
11 | @echo TEST-PASS
12 |
--------------------------------------------------------------------------------
/tests/native-command-shell-glob.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 | all:
3 | mkdir shell-glob-test
4 | touch shell-glob-test/foo.txt
5 | touch shell-glob-test/bar.txt
6 | touch shell-glob-test/a.foo
7 | touch shell-glob-test/b.foo
8 | $(RM) shell-glob-test/*.txt
9 | $(RM) shell-glob-test/?.foo
10 | rmdir shell-glob-test
11 | @echo TEST-PASS
12 |
--------------------------------------------------------------------------------
/tests/native-command-sys-exit-fail1.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 | #T returncode: 2
3 |
4 | CMD = %pycmd asplode
5 | PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
6 |
7 | all:
8 | $(CMD) 1
9 |
--------------------------------------------------------------------------------
/tests/native-command-sys-exit-fail2.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 | #T returncode: 2
3 |
4 | CMD = %pycmd asplode
5 | PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
6 |
7 | all:
8 | $(CMD) not-an-integer
9 |
--------------------------------------------------------------------------------
/tests/native-command-sys-exit.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 |
3 | CMD = %pycmd asplode
4 | PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
5 |
6 | all:
7 | $(CMD) 0
8 | -$(CMD) 1
9 | $(CMD) None
10 | -$(CMD) not-an-integer
11 | @echo TEST-PASS
12 |
--------------------------------------------------------------------------------
/tests/native-environment.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 | export EXPECTED := some data
3 |
4 | PYCOMMANDPATH = $(TESTPATH)
5 |
6 | all:
7 | %pycmd writeenvtofile results EXPECTED
8 | test "$$(cat results)" = "$(EXPECTED)"
9 | %pycmd writesubprocessenvtofile results EXPECTED
10 | test "$$(cat results)" = "$(EXPECTED)"
11 | @echo TEST-PASS
12 |
--------------------------------------------------------------------------------
/tests/native-pycommandpath-sep.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 | EXPECTED := some data
3 |
4 | # verify that we can load native command modules from
5 | # multiple directories in PYCOMMANDPATH separated by the native
6 | # path separator
7 | ifdef __WIN32__
8 | PS:=;
9 | else
10 | PS:=:
11 | endif
12 | CMD = %pycmd writetofile
13 | CMD2 = %pymod writetofile
14 | PYCOMMANDPATH = $(TESTPATH)$(PS)$(TESTPATH)/subdir
15 |
16 | all:
17 | $(CMD) results $(EXPECTED)
18 | test "$$(cat results)" = "$(EXPECTED)"
19 | $(CMD2) results2 $(EXPECTED)
20 | test "$$(cat results2)" = "$(EXPECTED)"
21 | @echo TEST-PASS
22 |
--------------------------------------------------------------------------------
/tests/native-pycommandpath.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 | EXPECTED := some data
3 |
4 | # verify that we can load native command modules from
5 | # multiple space-separated directories in PYCOMMANDPATH
6 | CMD = %pycmd writetofile
7 | CMD2 = %pymod writetofile
8 | PYCOMMANDPATH = $(TESTPATH) $(TESTPATH)/subdir
9 |
10 | all:
11 | $(CMD) results $(EXPECTED)
12 | test "$$(cat results)" = "$(EXPECTED)"
13 | $(CMD2) results2 $(EXPECTED)
14 | test "$$(cat results2)" = "$(EXPECTED)"
15 | @echo TEST-PASS
16 |
--------------------------------------------------------------------------------
/tests/native-simple.mk:
--------------------------------------------------------------------------------
1 | ifndef TOUCH
2 | TOUCH = touch
3 | endif
4 |
5 | all: testfile {testfile2} (testfile3)
6 | test -f testfile
7 | test -f {testfile2}
8 | test -f "(testfile3)"
9 | @echo TEST-PASS
10 |
11 | testfile {testfile2} (testfile3):
12 | $(TOUCH) "$@"
13 |
--------------------------------------------------------------------------------
/tests/native-touch.mk:
--------------------------------------------------------------------------------
1 | TOUCH ?= touch
2 |
3 | foo:
4 | $(TOUCH) bar
5 | $(TOUCH) baz
6 | $(MAKE) -f $(TESTPATH)/native-touch.mk baz
7 | $(TOUCH) -t 198007040802 baz
8 | $(MAKE) -f $(TESTPATH)/native-touch.mk baz
9 |
10 | bar:
11 | $(TOUCH) $@
12 |
13 | baz: bar
14 | echo TEST-PASS
15 | $(TOUCH) $@
16 |
--------------------------------------------------------------------------------
/tests/newlines.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 |
3 | # Test that we handle \\\n properly
4 |
5 | all: dep1 dep2 dep3
6 | cat testfile
7 | test `cat testfile` = "data";
8 | test "$$(cat results)" = "$(EXPECTED)";
9 | @echo TEST-PASS
10 |
11 | # Test that something that still needs to go to the shell works
12 | testfile:
13 | printf "data" \
14 | >>$@
15 |
16 | dep1: testfile
17 |
18 | # Test that something that does not need to go to the shell works
19 | dep2:
20 | $(echo foo) \
21 | $(echo bar)
22 |
23 | export EXPECTED := some data
24 |
25 | CMD = %pycmd writeenvtofile
26 | PYCOMMANDPATH = $(TESTPATH)
27 |
28 | dep3:
29 | $(CMD) \
30 | results EXPECTED
31 |
--------------------------------------------------------------------------------
/tests/no-remake.mk:
--------------------------------------------------------------------------------
1 | $(shell date >testfile)
2 |
3 | all: testfile
4 | @echo TEST-PASS
5 |
6 | testfile:
7 | @echo TEST-FAIL "We shouldn't have remade this!"
8 |
--------------------------------------------------------------------------------
/tests/nosuchfile.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | all:
4 | reallythereisnosuchcommand
5 |
--------------------------------------------------------------------------------
/tests/notargets.mk:
--------------------------------------------------------------------------------
1 | $(NULL): foo.c
2 | @echo TEST-FAIL
3 |
4 | all:
5 | @echo TEST-PASS
6 |
--------------------------------------------------------------------------------
/tests/notparallel.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-j3']
2 |
3 | include $(TESTPATH)/serial-rule-execution.mk
4 |
5 | all::
6 | $(MAKE) -f $(TESTPATH)/parallel-simple.mk
7 |
8 | .NOTPARALLEL:
9 |
--------------------------------------------------------------------------------
/tests/oneline-command-continuations.mk:
--------------------------------------------------------------------------------
1 | all: test
2 | @echo TEST-PASS
3 |
4 | test: ; test "Hello \
5 | world" = "Hello world"
6 |
--------------------------------------------------------------------------------
/tests/override-propagate.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-w', 'OVAR=oval']
2 |
3 | OVAR=mval
4 |
5 | all: vartest run-override
6 | $(MAKE) -f $(TESTPATH)/override-propagate.mk vartest
7 | @echo TEST-PASS
8 |
9 | CLINE := OVAR=oval TESTPATH=$(TESTPATH) NATIVE_TESTPATH=$(NATIVE_TESTPATH)
10 | ifdef __WIN32__
11 | CLINE += __WIN32__=1
12 | endif
13 |
14 | SORTED_CLINE := $(subst \,\\,$(sort $(CLINE)))
15 |
16 | vartest:
17 | @echo MAKELEVEL: '$(MAKELEVEL)'
18 | test '$(value MAKEFLAGS)' = 'w -- $$(MAKEOVERRIDES)'
19 | test '$(origin MAKEFLAGS)' = 'file'
20 | test '$(value MAKEOVERRIDES)' = '$${-*-command-variables-*-}'
21 | test "$(sort $(MAKEOVERRIDES))" = "$(SORTED_CLINE)"
22 | test '$(origin MAKEOVERRIDES)' = 'environment'
23 | test '$(origin -*-command-variables-*-)' = 'automatic'
24 | test "$(origin OVAR)" = "command line"
25 | test "$(OVAR)" = "oval"
26 |
27 | run-override: MAKEOVERRIDES=
28 | run-override:
29 | test "$(OVAR)" = "oval"
30 | $(MAKE) -f $(TESTPATH)/override-propagate.mk otest
31 |
32 | otest:
33 | test '$(value MAKEFLAGS)' = 'w'
34 | test '$(value MAKEOVERRIDES)' = '$${-*-command-variables-*-}'
35 | test '$(MAKEOVERRIDES)' = ''
36 | test '$(origin -*-command-variables-*-)' = 'undefined'
37 | test "$(OVAR)" = "mval"
38 |
--------------------------------------------------------------------------------
/tests/parallel-dep-resolution.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-j3']
2 | #T returncode: 2
3 |
4 | all: t1 t2
5 |
6 | t1:
7 | sleep 1
8 | touch t1 t2
9 |
--------------------------------------------------------------------------------
/tests/parallel-dep-resolution2.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-j3']
2 | #T returncode: 2
3 |
4 | all::
5 | sleep 1
6 | touch somefile
7 |
8 | all:: somefile
9 | @echo TEST-PASS
10 |
--------------------------------------------------------------------------------
/tests/parallel-native.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-j2']
2 |
3 | # ensure that calling python commands doesn't block other targets
4 | ifndef SLEEP
5 | SLEEP := sleep
6 | endif
7 |
8 | PRINTF = printf "$@:0:" >>results
9 | EXPECTED = target2:0:target1:0:
10 |
11 | all:: target1 target2
12 | cat results
13 | test "$$(cat results)" = "$(EXPECTED)"
14 | @echo TEST-PASS
15 |
16 | target1:
17 | $(SLEEP) 0.1
18 | $(PRINTF)
19 |
20 | target2:
21 | $(PRINTF)
22 |
--------------------------------------------------------------------------------
/tests/parallel-simple.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-j2']
2 |
3 | # CAUTION: this makefile is also used by serial-toparallel.mk
4 |
5 | define SLOWMAKE
6 | printf "$@:0:" >>results
7 | sleep 0.5
8 | printf "$@:1:" >>results
9 | sleep 0.5
10 | printf "$@:2:" >>results
11 | endef
12 |
13 | EXPECTED = target1:0:target2:0:target1:1:target2:1:target1:2:target2:2:
14 |
15 | all:: target1 target2
16 | cat results
17 | test "$$(cat results)" = "$(EXPECTED)"
18 | @echo TEST-PASS
19 |
20 | target1:
21 | $(SLOWMAKE)
22 |
23 | target2:
24 | sleep 0.1
25 | $(SLOWMAKE)
26 |
27 | .PHONY: all
28 |
--------------------------------------------------------------------------------
/tests/parallel-submake.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-j2']
2 |
3 | # A submake shouldn't return control to the parent until it has actually finished doing everything.
4 |
5 | all:
6 | -$(MAKE) -f $(TESTPATH)/parallel-submake.mk subtarget
7 | cat results
8 | test "$$(cat results)" = "0123"
9 | @echo TEST-PASS
10 |
11 | subtarget: succeed-slowly fail-quickly
12 |
13 | succeed-slowly:
14 | printf 0 >>results; sleep 1; printf 1 >>results; sleep 1; printf 2 >>results; sleep 1; printf 3 >>results
15 |
16 | fail-quickly:
17 | exit 1
18 |
--------------------------------------------------------------------------------
/tests/parallel-toserial.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-j4']
2 |
3 | # Test that -j1 in a submake has the proper effect.
4 |
5 | define SLOWCMD
6 | printf "$@:0:" >>$(RFILE)
7 | sleep 0.5
8 | printf "$@:1:" >>$(RFILE)
9 | endef
10 |
11 | all: p1 p2
12 | subtarget: s1 s2
13 |
14 | p1 p2: RFILE = presult
15 | s1 s2: RFILE = sresult
16 |
17 | p1 s1:
18 | $(SLOWCMD)
19 |
20 | p2 s2:
21 | sleep 0.1
22 | $(SLOWCMD)
23 |
24 | all:
25 | $(MAKE) -j1 -f $(TESTPATH)/parallel-toserial.mk subtarget
26 | printf "presult: %s\n" "$$(cat presult)"
27 | test "$$(cat presult)" = "p1:0:p2:0:p1:1:p2:1:"
28 | printf "sresult: %s\n" "$$(cat sresult)"
29 | test "$$(cat sresult)" = "s1:0:s1:1:s2:0:s2:1:"
30 | @echo TEST-PASS
31 |
32 |
--------------------------------------------------------------------------------
/tests/parallel-waiting.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-j2']
2 |
3 | EXPECTED = target1:before:target2:1:target2:2:target2:3:target1:after
4 |
5 | all:: target1 target2
6 | cat results
7 | test "$$(cat results)" = "$(EXPECTED)"
8 | @echo TEST-PASS
9 |
10 | target1:
11 | printf "$@:before:" >>results
12 | sleep 4
13 | printf "$@:after" >>results
14 |
15 | target2:
16 | sleep 0.2
17 | printf "$@:1:" >>results
18 | sleep 0.1
19 | printf "$@:2:" >>results
20 | sleep 0.1
21 | printf "$@:3:" >>results
22 |
--------------------------------------------------------------------------------
/tests/parentheses.mk:
--------------------------------------------------------------------------------
1 | all:
2 | @(echo TEST-PASS)
3 |
--------------------------------------------------------------------------------
/tests/parsertests.py:
--------------------------------------------------------------------------------
1 | import pymake.data, pymake.parser, pymake.parserdata, pymake.functions
2 | import unittest
3 | import logging
4 |
5 |
6 | def multitest(cls):
7 | for name in cls.testdata.keys():
8 | def m(self, name=name):
9 | return self.runSingle(*self.testdata[name])
10 |
11 | setattr(cls, 'test_%s' % name, m)
12 | return cls
13 |
14 | class TestBase(unittest.TestCase):
15 | def assertEqual(self, a, b, msg=""):
16 | """Actually print the values which weren't equal, if things don't work out!"""
17 | unittest.TestCase.assertEqual(self, a, b, "%s got %r expected %r" % (msg, a, b))
18 |
19 | class DataTest(TestBase):
20 | testdata = {
21 | 'oneline':
22 | ("He\tllo", "f", 1, 0,
23 | ((0, "f", 1, 0), (2, "f", 1, 2), (3, "f", 1, 4))),
24 | 'twoline':
25 | ("line1 \n\tl\tine2", "f", 1, 4,
26 | ((0, "f", 1, 4), (5, "f", 1, 9), (6, "f", 1, 10), (7, "f", 2, 0), (8, "f", 2, 4), (10, "f", 2, 8), (13, "f", 2, 11))),
27 | }
28 |
29 | def runSingle(self, data, filename, line, col, results):
30 | d = pymake.parser.Data(data, 0, len(data), pymake.parserdata.Location(filename, line, col))
31 | for pos, file, lineno, col in results:
32 | loc = d.getloc(pos)
33 | self.assertEqual(loc.path, file, "data file offset %i" % pos)
34 | self.assertEqual(loc.line, lineno, "data line offset %i" % pos)
35 | self.assertEqual(loc.column, col, "data col offset %i" % pos)
36 | multitest(DataTest)
37 |
38 | class LineEnumeratorTest(TestBase):
39 | testdata = {
40 | 'simple': (
41 | 'Hello, world', [
42 | ('Hello, world', 1),
43 | ]
44 | ),
45 | 'multi': (
46 | 'Hello\nhappy \n\nworld\n', [
47 | ('Hello', 1),
48 | ('happy ', 2),
49 | ('', 3),
50 | ('world', 4),
51 | ('', 5),
52 | ]
53 | ),
54 | 'continuation': (
55 | 'Hello, \\\n world\nJellybeans!', [
56 | ('Hello, \\\n world', 1),
57 | ('Jellybeans!', 3),
58 | ]
59 | ),
60 | 'multislash': (
61 | 'Hello, \\\\\n world', [
62 | ('Hello, \\\\', 1),
63 | (' world', 2),
64 | ]
65 | )
66 | }
67 |
68 | def runSingle(self, s, lines):
69 | gotlines = [(d.s[d.lstart:d.lend], d.loc.line) for d in pymake.parser.enumeratelines(s, 'path')]
70 | self.assertEqual(gotlines, lines)
71 |
72 | multitest(LineEnumeratorTest)
73 |
74 | class IterTest(TestBase):
75 | testdata = {
76 | 'plaindata': (
77 | pymake.parser.iterdata,
78 | "plaindata # test\n",
79 | "plaindata # test\n"
80 | ),
81 | 'makecomment': (
82 | pymake.parser.itermakefilechars,
83 | "VAR = val # comment",
84 | "VAR = val "
85 | ),
86 | 'makeescapedcomment': (
87 | pymake.parser.itermakefilechars,
88 | "VAR = val \# escaped hash",
89 | "VAR = val # escaped hash"
90 | ),
91 | 'makeescapedslash': (
92 | pymake.parser.itermakefilechars,
93 | "VAR = val\\\\",
94 | "VAR = val\\\\",
95 | ),
96 | 'makecontinuation': (
97 | pymake.parser.itermakefilechars,
98 | "VAR = VAL \\\n continuation # comment \\\n continuation",
99 | "VAR = VAL continuation "
100 | ),
101 | 'makecontinuation2': (
102 | pymake.parser.itermakefilechars,
103 | "VAR = VAL \\ \\\n continuation",
104 | "VAR = VAL \\ continuation"
105 | ),
106 | 'makeawful': (
107 | pymake.parser.itermakefilechars,
108 | "VAR = VAL \\\\# comment\n",
109 | "VAR = VAL \\"
110 | ),
111 | 'command': (
112 | pymake.parser.itercommandchars,
113 | "echo boo # comment",
114 | "echo boo # comment",
115 | ),
116 | 'commandcomment': (
117 | pymake.parser.itercommandchars,
118 | "echo boo \# comment",
119 | "echo boo \# comment",
120 | ),
121 | 'commandcontinue': (
122 | pymake.parser.itercommandchars,
123 | "echo boo # \\\n\t command 2",
124 | "echo boo # \\\n command 2"
125 | ),
126 | }
127 |
128 | def runSingle(self, ifunc, idata, expected):
129 | d = pymake.parser.Data.fromstring(idata, 'IterTest data')
130 |
131 | it = pymake.parser._alltokens.finditer(d.s, 0, d.lend)
132 | actual = ''.join( [c for c, t, o, oo in ifunc(d, 0, ('dummy-token',), it)] )
133 | self.assertEqual(actual, expected)
134 |
135 | if ifunc == pymake.parser.itermakefilechars:
136 | print("testing %r" % expected)
137 | self.assertEqual(pymake.parser.flattenmakesyntax(d, 0), expected)
138 |
139 | multitest(IterTest)
140 |
141 |
142 | # 'define': (
143 | # pymake.parser.iterdefinechars,
144 | # "endef",
145 | # ""
146 | # ),
147 | # 'definenesting': (
148 | # pymake.parser.iterdefinechars,
149 | # """define BAR # comment
150 | #random text
151 | #endef not what you think!
152 | #endef # comment is ok\n""",
153 | # """define BAR # comment
154 | #random text
155 | #endef not what you think!"""
156 | # ),
157 | # 'defineescaped': (
158 | # pymake.parser.iterdefinechars,
159 | # """value \\
160 | #endef
161 | #endef\n""",
162 | # "value endef"
163 | # ),
164 |
165 | class MakeSyntaxTest(TestBase):
166 | # (string, startat, stopat, stopoffset, expansion
167 | testdata = {
168 | 'text': ('hello world', 0, (), None, ['hello world']),
169 | 'singlechar': ('hello $W', 0, (), None,
170 | ['hello ',
171 | {'type': 'VariableRef',
172 | '.vname': ['W']}
173 | ]),
174 | 'stopat': ('hello: world', 0, (':', '='), 6, ['hello']),
175 | 'funccall': ('h $(flavor FOO)', 0, (), None,
176 | ['h ',
177 | {'type': 'FlavorFunction',
178 | '[0]': ['FOO']}
179 | ]),
180 | 'escapedollar': ('hello$$world', 0, (), None, ['hello$world']),
181 | 'varref': ('echo $(VAR)', 0, (), None,
182 | ['echo ',
183 | {'type': 'VariableRef',
184 | '.vname': ['VAR']}
185 | ]),
186 | 'dynamicvarname': ('echo $($(VARNAME):.c=.o)', 0, (':',), None,
187 | ['echo ',
188 | {'type': 'SubstitutionRef',
189 | '.vname': [{'type': 'VariableRef',
190 | '.vname': ['VARNAME']}
191 | ],
192 | '.substfrom': ['.c'],
193 | '.substto': ['.o']}
194 | ]),
195 | 'substref': (' $(VAR:VAL) := $(VAL)', 0, (':=', '+=', '=', ':'), 15,
196 | [' ',
197 | {'type': 'VariableRef',
198 | '.vname': ['VAR:VAL']},
199 | ' ']),
200 | 'vadsubstref': (' $(VAR:VAL) = $(VAL)', 15, (), None,
201 | [{'type': 'VariableRef',
202 | '.vname': ['VAL']},
203 | ]),
204 | }
205 |
206 | def compareRecursive(self, actual, expected, path):
207 | self.assertEqual(len(actual), len(expected),
208 | "compareRecursive: %s %r" % (path, actual))
209 | for i in range(0, len(actual)):
210 | ipath = path + [i]
211 |
212 | a, isfunc = actual[i]
213 | e = expected[i]
214 | if isinstance(e, str):
215 | self.assertEqual(a, e, "compareRecursive: %s" % (ipath,))
216 | else:
217 | self.assertEqual(type(a), getattr(pymake.functions, e['type']),
218 | "compareRecursive: %s" % (ipath,))
219 | for k, v in e.items():
220 | if k == 'type':
221 | pass
222 | elif k[0] == '[':
223 | item = int(k[1:-1])
224 | proppath = ipath + [item]
225 | self.compareRecursive(a[item], v, proppath)
226 | elif k[0] == '.':
227 | item = k[1:]
228 | proppath = ipath + [item]
229 | self.compareRecursive(getattr(a, item), v, proppath)
230 | else:
231 | raise Exception("Unexpected property at %s: %s" % (ipath, k))
232 |
233 | def runSingle(self, s, startat, stopat, stopoffset, expansion):
234 | d = pymake.parser.Data.fromstring(s, pymake.parserdata.Location('testdata', 1, 0))
235 |
236 | a, t, offset = pymake.parser.parsemakesyntax(d, startat, stopat, pymake.parser.itermakefilechars)
237 | self.compareRecursive(a, expansion, [])
238 | self.assertEqual(offset, stopoffset)
239 |
240 | multitest(MakeSyntaxTest)
241 |
242 | class VariableTest(TestBase):
243 | testdata = """
244 | VAR = value
245 | VARNAME = TESTVAR
246 | $(VARNAME) = testvalue
247 | $(VARNAME:VAR=VAL) = moretesting
248 | IMM := $(VARNAME) # this is a comment
249 | MULTIVAR = val1 \\
250 | val2
251 | VARNAME = newname
252 | """
253 | expected = {'VAR': 'value',
254 | 'VARNAME': 'newname',
255 | 'TESTVAR': 'testvalue',
256 | 'TESTVAL': 'moretesting',
257 | 'IMM': 'TESTVAR ',
258 | 'MULTIVAR': 'val1 val2',
259 | 'UNDEF': None}
260 |
261 | def runTest(self):
262 | stmts = pymake.parser.parsestring(self.testdata, 'VariableTest')
263 |
264 | m = pymake.data.Makefile()
265 | stmts.execute(m)
266 | for k, v in self.expected.items():
267 | flavor, source, val = m.variables.get(k)
268 | if val is None:
269 | self.assertEqual(val, v, 'variable named %s' % k)
270 | else:
271 | self.assertEqual(val.resolvestr(m, m.variables), v, 'variable named %s' % k)
272 |
273 | class SimpleRuleTest(TestBase):
274 | testdata = """
275 | VAR = value
276 | TSPEC = dummy
277 | all: TSPEC = myrule
278 | all:: test test2 $(VAR)
279 | echo "Hello, $(TSPEC)"
280 |
281 | %.o: %.c
282 | $(CC) -o $@ $<
283 | """
284 |
285 | def runTest(self):
286 | stmts = pymake.parser.parsestring(self.testdata, 'SimpleRuleTest')
287 |
288 | m = pymake.data.Makefile()
289 | stmts.execute(m)
290 | self.assertEqual(m.defaulttarget, 'all', "Default target")
291 |
292 | self.assertTrue(m.hastarget('all'), "Has 'all' target")
293 | target = m.gettarget('all')
294 | rules = target.rules
295 | self.assertEqual(len(rules), 1, "Number of rules")
296 | prereqs = rules[0].prerequisites
297 | self.assertEqual(prereqs, ['test', 'test2', 'value'], "Prerequisites")
298 | commands = rules[0].commands
299 | self.assertEqual(len(commands), 1, "Number of commands")
300 | expanded = commands[0].resolvestr(m, target.variables)
301 | self.assertEqual(expanded, 'echo "Hello, myrule"')
302 |
303 | irules = m.implicitrules
304 | self.assertEqual(len(irules), 1, "Number of implicit rules")
305 |
306 | irule = irules[0]
307 | self.assertEqual(len(irule.targetpatterns), 1, "%.o target pattern count")
308 | self.assertEqual(len(irule.prerequisites), 1, "%.o prerequisite count")
309 | self.assertEqual(irule.targetpatterns[0].match('foo.o'), 'foo', "%.o stem")
310 |
311 | if __name__ == '__main__':
312 | logging.basicConfig(level=logging.DEBUG)
313 | unittest.main()
314 |
--------------------------------------------------------------------------------
/tests/path-length.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 |
3 | $(shell \
4 | mkdir foo; \
5 | touch tfile; \
6 | )
7 |
8 | all: foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../foo/../tfile
9 | @echo TEST-PASS
10 |
--------------------------------------------------------------------------------
/tests/pathdir/pathtest:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | echo Called shell script: 2f7cdd0b-7277-48c1-beaf-56cb0dbacb24
3 |
--------------------------------------------------------------------------------
/tests/pathdir/pathtest.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/pymake/034ae9ea5b726e03647d049147c5dbf688e94aaf/tests/pathdir/pathtest.exe
--------------------------------------------------------------------------------
/tests/pathdir/src/Makefile:
--------------------------------------------------------------------------------
1 | pathtest.exe: pathtest.cpp
2 | cl -EHsc -MT $^
3 |
--------------------------------------------------------------------------------
/tests/pathdir/src/pathtest.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | int main() {
4 | std::printf("Called Windows executable: 2f7cdd0b-7277-48c1-beaf-56cb0dbacb24\n");
5 | return 0;
6 | }
7 |
--------------------------------------------------------------------------------
/tests/patsubst.mk:
--------------------------------------------------------------------------------
1 | all:
2 | test "$(patsubst foo,%.bar,foo)" = "%.bar"
3 | test "$(patsubst \%word,replace,word %word other)" = "word replace other"
4 | test "$(patsubst %.c,\%%.o,foo.c bar.o baz.cpp)" = "%foo.o bar.o baz.cpp"
5 | test "$(patsubst host_%.c,host_%.o,dir/host_foo.c host_bar.c)" = "dir/host_foo.c host_bar.o"
6 | test "$(patsubst foo,bar,dir/foo foo baz)" = "dir/foo bar baz"
7 | @echo TEST-PASS
8 |
--------------------------------------------------------------------------------
/tests/phony.mk:
--------------------------------------------------------------------------------
1 | $(shell \
2 | touch dep; \
3 | sleep 2; \
4 | touch all; \
5 | )
6 |
7 | all:: dep
8 | @echo TEST-PASS
9 |
10 | .PHONY: all
11 |
--------------------------------------------------------------------------------
/tests/pycmd.py:
--------------------------------------------------------------------------------
1 | import os, sys, subprocess
2 |
3 | def writetofile(args):
4 | with open(args[0], 'w') as f:
5 | f.write(' '.join(args[1:]))
6 |
7 | def writeenvtofile(args):
8 | with open(args[0], 'w') as f:
9 | f.write(os.environ[args[1]])
10 |
11 | def writesubprocessenvtofile(args):
12 | with open(args[0], 'w') as f:
13 | p = subprocess.Popen([sys.executable, "-c",
14 | "import os; print os.environ['%s']" % args[1]],
15 | stdout=subprocess.PIPE, stderr=subprocess.PIPE)
16 | stdout, stderr = p.communicate()
17 | assert p.returncode == 0
18 | f.write(stdout)
19 |
20 | def convertasplode(arg):
21 | try:
22 | return int(arg)
23 | except:
24 | return (None if arg == "None" else arg)
25 |
26 | def asplode(args):
27 | arg0 = convertasplode(args[0])
28 | sys.exit(arg0)
29 |
30 | def asplode_return(args):
31 | arg0 = convertasplode(args[0])
32 | return arg0
33 |
34 | def asplode_raise(args):
35 | raise Exception(args[0])
36 |
37 | def delayloadfn(args):
38 | import delayload
39 |
--------------------------------------------------------------------------------
/tests/recursive-set.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | FOO = $(FOO)
4 |
5 | all:
6 | echo $(FOO)
7 | @echo TEST-FAIL
8 |
--------------------------------------------------------------------------------
/tests/recursive-set2.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | FOO = $(BAR)
4 | BAR = $(FOO)
5 |
6 | all:
7 | echo $(FOO)
8 | @echo TEST-FAIL
9 |
--------------------------------------------------------------------------------
/tests/remake-mtime.mk:
--------------------------------------------------------------------------------
1 | # mtime(dep1) < mtime(target) so the target should not be made
2 | $(shell touch dep1; sleep 1; touch target)
3 |
4 | all: target
5 | echo TEST-PASS
6 |
7 | target: dep1
8 | echo TEST-FAIL target should not have been made
9 |
10 | dep1: dep2
11 | @echo "Remaking dep1 (actually not)"
12 |
13 | dep2:
14 | @echo "Making dep2 (actually not)"
15 |
--------------------------------------------------------------------------------
/tests/rm-fail.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 | all:
3 | mkdir newdir
4 | test -d newdir
5 | touch newdir/newfile
6 | $(RM) newdir
7 | @echo TEST-PASS
8 |
--------------------------------------------------------------------------------
/tests/rm.mk:
--------------------------------------------------------------------------------
1 | all:
2 | # $(RM) defaults to -f
3 | $(RM) nosuchfile
4 | touch newfile
5 | test -f newfile
6 | $(RM) newfile
7 | test ! -f newfile
8 | mkdir newdir
9 | test -d newdir
10 | touch newdir/newfile
11 | mkdir newdir/subdir
12 | $(RM) -r newdir/subdir
13 | test ! -d newdir/subdir
14 | test -d newdir
15 | mkdir newdir/subdir1 newdir/subdir2
16 | $(RM) -r newdir/subdir1 newdir/subdir2
17 | test ! -d newdir/subdir1 -a ! -d newdir/subdir2
18 | test -d newdir
19 | $(RM) -r newdir
20 | test ! -d newdir
21 | @echo TEST-PASS
22 |
--------------------------------------------------------------------------------
/tests/runtests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """
3 | Run the test(s) listed on the command line. If a directory is listed, the script will recursively
4 | walk the directory for files named .mk and run each.
5 |
6 | For each test, we run gmake -f test.mk. By default, make must exit with an exit code of 0, and must print 'TEST-PASS'.
7 |
8 | Each test is run in an empty directory.
9 |
10 | The test file may contain lines at the beginning to alter the default behavior. These are all evaluated as python:
11 |
12 | #T commandline: ['extra', 'params', 'here']
13 | #T returncode: 2
14 | #T returncode-on: {'win32': 2}
15 | #T environment: {'VAR': 'VALUE}
16 | #T grep-for: "text"
17 | """
18 | from __future__ import print_function
19 |
20 | from subprocess import Popen, PIPE, STDOUT
21 | from optparse import OptionParser
22 | import os, re, sys, shutil, glob
23 |
24 | class ParentDict(dict):
25 | def __init__(self, parent, **kwargs):
26 | self.d = dict(kwargs)
27 | self.parent = parent
28 |
29 | def __setitem__(self, k, v):
30 | self.d[k] = v
31 |
32 | def __getitem__(self, k):
33 | if k in self.d:
34 | return self.d[k]
35 |
36 | return self.parent[k]
37 |
38 | thisdir = os.path.dirname(os.path.abspath(__file__))
39 |
40 | pymake = [sys.executable, os.path.join(os.path.dirname(thisdir), 'make.py')]
41 | manifest = os.path.join(thisdir, 'tests.manifest')
42 |
43 | o = OptionParser()
44 | o.add_option('-g', '--gmake',
45 | dest="gmake", default="gmake")
46 | o.add_option('-d', '--tempdir',
47 | dest="tempdir", default="_mktests")
48 | opts, args = o.parse_args()
49 |
50 | if len(args) == 0:
51 | args = [thisdir]
52 |
53 | makefiles = []
54 | for a in args:
55 | if os.path.isfile(a):
56 | makefiles.append(a)
57 | elif os.path.isdir(a):
58 | makefiles.extend(sorted(glob.glob(os.path.join(a, '*.mk'))))
59 |
60 | def runTest(makefile, make, logfile, options):
61 | """
62 | Given a makefile path, test it with a given `make` and return
63 | (pass, message).
64 | """
65 |
66 | if os.path.exists(opts.tempdir): shutil.rmtree(opts.tempdir)
67 | os.mkdir(opts.tempdir, 0o755)
68 |
69 | logfd = open(logfile, 'w')
70 | p = Popen(make + options['commandline'], stdout=logfd, stderr=STDOUT, env=options['env'])
71 | logfd.close()
72 | retcode = p.wait()
73 |
74 | if retcode != options['returncode']:
75 | return False, "FAIL (returncode=%i)" % retcode
76 |
77 | logfd = open(logfile)
78 | stdout = logfd.read()
79 | logfd.close()
80 |
81 | if stdout.find('TEST-FAIL') != -1:
82 | print(stdout)
83 | return False, "FAIL (TEST-FAIL printed)"
84 |
85 | if options['grepfor'] and stdout.find(options['grepfor']) == -1:
86 | print(stdout)
87 | return False, "FAIL (%s not in output)" % options['grepfor']
88 |
89 | if options['returncode'] == 0 and stdout.find('TEST-PASS') == -1:
90 | print(stdout)
91 | return False, 'FAIL (No TEST-PASS printed)'
92 |
93 | if options['returncode'] != 0:
94 | return True, 'PASS (retcode=%s)' % retcode
95 |
96 | return True, 'PASS'
97 |
98 | print("%-30s%-28s%-28s" % ("Test:", "gmake:", "pymake:"))
99 |
100 | gmakefails = 0
101 | pymakefails = 0
102 |
103 | tre = re.compile('^#T (gmake |pymake )?([a-z-]+)(?:: (.*))?$')
104 |
105 | for makefile in makefiles:
106 | # For some reason, MAKEFILE_LIST uses native paths in GNU make on Windows
107 | # (even in MSYS!) so we pass both TESTPATH and NATIVE_TESTPATH
108 | cline = ['-C', opts.tempdir, '-f', os.path.abspath(makefile), 'TESTPATH=%s' % thisdir.replace('\\','/'), 'NATIVE_TESTPATH=%s' % thisdir]
109 | if sys.platform == 'win32':
110 | #XXX: hack so we can specialize the separator character on windows.
111 | # we really shouldn't need this, but y'know
112 | cline += ['__WIN32__=1']
113 |
114 | options = {
115 | 'returncode': 0,
116 | 'grepfor': None,
117 | 'env': dict(os.environ),
118 | 'commandline': cline,
119 | 'pass': True,
120 | 'skip': False,
121 | }
122 |
123 | gmakeoptions = ParentDict(options)
124 | pymakeoptions = ParentDict(options)
125 |
126 | dmap = {None: options, 'gmake ': gmakeoptions, 'pymake ': pymakeoptions}
127 |
128 | mdata = open(makefile)
129 | for line in mdata:
130 | line = line.strip()
131 | m = tre.search(line)
132 | if m is None:
133 | break
134 |
135 | make, key, data = m.group(1, 2, 3)
136 | d = dmap[make]
137 | if data is not None:
138 | data = eval(data)
139 | if key == 'commandline':
140 | assert make is None
141 | d['commandline'].extend(data)
142 | elif key == 'returncode':
143 | d['returncode'] = data
144 | elif key == 'returncode-on':
145 | if sys.platform in data:
146 | d['returncode'] = data[sys.platform]
147 | elif key == 'environment':
148 | for k, v in data.items():
149 | d['env'][k] = v
150 | elif key == 'grep-for':
151 | d['grepfor'] = data
152 | elif key == 'fail':
153 | d['pass'] = False
154 | elif key == 'skip':
155 | d['skip'] = True
156 | else:
157 | print("%s: Unexpected #T key: %s" % (makefile, key), file=sys.stderr)
158 | sys.exit(1)
159 |
160 | mdata.close()
161 |
162 | if gmakeoptions['skip']:
163 | gmakepass, gmakemsg = True, ''
164 | else:
165 | gmakepass, gmakemsg = runTest(makefile, [opts.gmake],
166 | makefile + '.gmakelog', gmakeoptions)
167 |
168 | if gmakeoptions['pass']:
169 | if not gmakepass:
170 | gmakefails += 1
171 | else:
172 | if gmakepass:
173 | gmakefails += 1
174 | gmakemsg = "UNEXPECTED PASS"
175 | else:
176 | gmakemsg = "KNOWN FAIL"
177 |
178 | if pymakeoptions['skip']:
179 | pymakepass, pymakemsg = True, ''
180 | else:
181 | pymakepass, pymakemsg = runTest(makefile, pymake,
182 | makefile + '.pymakelog', pymakeoptions)
183 |
184 | if pymakeoptions['pass']:
185 | if not pymakepass:
186 | pymakefails += 1
187 | else:
188 | if pymakepass:
189 | pymakefails += 1
190 | pymakemsg = "UNEXPECTED PASS"
191 | else:
192 | pymakemsg = "OK (known fail)"
193 |
194 | print("%-30.30s%-28.28s%-28.28s" % (os.path.basename(makefile),
195 | gmakemsg, pymakemsg))
196 |
197 | print()
198 | print("Summary:")
199 | print("%-30s%-28s%-28s" % ("", "gmake:", "pymake:"))
200 |
201 | if gmakefails == 0:
202 | gmakemsg = 'PASS'
203 | else:
204 | gmakemsg = 'FAIL (%i failures)' % gmakefails
205 |
206 | if pymakefails == 0:
207 | pymakemsg = 'PASS'
208 | else:
209 | pymakemsg = 'FAIL (%i failures)' % pymakefails
210 |
211 | print("%-30.30s%-28.28s%-28.28s" % ('', gmakemsg, pymakemsg))
212 |
213 | shutil.rmtree(opts.tempdir)
214 |
215 | if gmakefails or pymakefails:
216 | sys.exit(1)
217 |
--------------------------------------------------------------------------------
/tests/serial-dep-resolution.mk:
--------------------------------------------------------------------------------
1 | all: t1 t2
2 | @echo TEST-PASS
3 |
4 | t1:
5 | touch t1 t2
6 |
--------------------------------------------------------------------------------
/tests/serial-doublecolon-execution.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['-j3']
2 |
3 | # Commands of double-colon rules are always executed in order.
4 |
5 | all: dc
6 | cat status
7 | test "$$(cat status)" = "all1:all2:"
8 | @echo TEST-PASS
9 |
10 | dc:: slowt
11 | printf "all1:" >> status
12 |
13 | dc::
14 | sleep 0.2
15 | printf "all2:" >> status
16 |
17 | slowt:
18 | sleep 1
19 |
--------------------------------------------------------------------------------
/tests/serial-rule-execution.mk:
--------------------------------------------------------------------------------
1 | all::
2 | touch somefile
3 |
4 | all:: somefile
5 | @echo TEST-PASS
6 |
--------------------------------------------------------------------------------
/tests/serial-rule-execution2.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | # The dependencies of the command rule of a single-colon target are resolved before the rules without commands.
4 |
5 | all: export
6 |
7 | export:
8 | sleep 1
9 | touch somefile
10 |
11 | all: somefile
12 | test -f somefile
13 | @echo TEST-PASS
14 |
--------------------------------------------------------------------------------
/tests/serial-toparallel.mk:
--------------------------------------------------------------------------------
1 | all::
2 | $(MAKE) -j2 -f $(TESTPATH)/parallel-simple.mk
3 |
4 | all:: results
5 | @echo TEST-PASS
6 |
--------------------------------------------------------------------------------
/tests/shellfunc.mk:
--------------------------------------------------------------------------------
1 | all: testfile
2 | test "$(shell cat $<)" = "Hello world"
3 | test "$(shell printf "\n")" = ""
4 | @echo TEST-PASS
5 |
6 | testfile:
7 | printf "Hello\nworld\n" > $@
8 |
--------------------------------------------------------------------------------
/tests/simple-makeflags.mk:
--------------------------------------------------------------------------------
1 | # There once was a time when MAKEFLAGS=w without any following spaces would
2 | # cause us to treat w as a target, not a flag. Silly!
3 |
4 | MAKEFLAGS=w
5 |
6 | all:
7 | $(MAKE) -f $(TESTPATH)/simple-makeflags.mk subt
8 | @echo TEST-PASS
9 |
10 | subt:
11 |
--------------------------------------------------------------------------------
/tests/sort.mk:
--------------------------------------------------------------------------------
1 | # sort should remove duplicates
2 | all:
3 | @test "$(sort x a y b z c a z b x c y)" = "a b c x y z"
4 | @echo "TEST-PASS"
5 |
--------------------------------------------------------------------------------
/tests/specified-target.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['VAR=all', '$(VAR)']
2 |
3 | all:
4 | @echo TEST-FAIL: unexpected target 'all'
5 |
6 | $$(VAR):
7 | @echo TEST-PASS: expected target '$$(VAR)'
8 |
--------------------------------------------------------------------------------
/tests/static-pattern.mk:
--------------------------------------------------------------------------------
1 | #T returncode: 2
2 |
3 | out/host_foo.o: host_%.o: host_%.c out
4 | cp $< $@
5 | @echo TEST-FAIL
6 |
--------------------------------------------------------------------------------
/tests/static-pattern2.mk:
--------------------------------------------------------------------------------
1 | all: foo.out
2 | test -f $^
3 | @echo TEST-PASS
4 |
5 | foo.out: %.out: %.in
6 | test "$*" = "foo"
7 | cp $^ $@
8 |
9 | foo.in:
10 | touch $@
11 |
--------------------------------------------------------------------------------
/tests/subdir/delayload.py:
--------------------------------------------------------------------------------
1 | # This module exists to test delay importing of modules at run-time.
2 |
--------------------------------------------------------------------------------
/tests/subdir/pymod.py:
--------------------------------------------------------------------------------
1 | import testmodule
2 |
3 | def writetofile(args):
4 | with open(args[0], 'w') as f:
5 | f.write(' '.join(args[1:]))
6 |
--------------------------------------------------------------------------------
/tests/subdir/testmodule.py:
--------------------------------------------------------------------------------
1 | # This is an empty module. It is imported by pymod.py to test that if a module
2 | # is loaded from the PYCOMMANDPATH, it can import other modules from the same
3 | # directory correctly.
4 |
--------------------------------------------------------------------------------
/tests/submake-path.makefile2:
--------------------------------------------------------------------------------
1 | # -*- Mode: Makefile -*-
2 |
3 | shellresult := $(shell pathtest)
4 | ifneq (2f7cdd0b-7277-48c1-beaf-56cb0dbacb24,$(filter $(shellresult),2f7cdd0b-7277-48c1-beaf-56cb0dbacb24))
5 | $(error pathtest not found in submake shell function)
6 | endif
7 |
8 | all:
9 | @pathtest
10 | @pathtest | grep -q 2f7cdd0b-7277-48c1-beaf-56cb0dbacb24
11 | @echo TEST-PASS
12 |
--------------------------------------------------------------------------------
/tests/submake-path.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 | #T grep-for: "2f7cdd0b-7277-48c1-beaf-56cb0dbacb24"
3 |
4 | ifdef __WIN32__
5 | PS:=;
6 | else
7 | PS:=:
8 | endif
9 |
10 | export PATH := $(TESTPATH)/pathdir$(PS)$(PATH)
11 |
12 | # This is similar to subprocess-path.mk, except we also check $(shell)
13 | # invocations since they're affected by exported environment variables too,
14 | # but only in submakes!
15 | all:
16 | $(MAKE) -f $(TESTPATH)/submake-path.makefile2
17 |
--------------------------------------------------------------------------------
/tests/submake.makefile2:
--------------------------------------------------------------------------------
1 | # -*- Mode: Makefile -*-
2 |
3 | $(info MAKEFLAGS = '$(MAKEFLAGS)')
4 | $(info MAKE = '$(MAKE)')
5 | $(info value MAKE = "$(value MAKE)")
6 |
7 | shellresult := $(shell echo -n $$EVAR)
8 | ifneq ($(shellresult),eval)
9 | $(error EVAR should be eval, is instead $(shellresult))
10 | endif
11 |
12 | all:
13 | env
14 | test "$(MAKELEVEL)" = "1"
15 | echo "value(MAKE)" '$(value MAKE)'
16 | echo "value(MAKE_COMMAND)" = '$(value MAKE_COMMAND)'
17 | test "$(origin CVAR)" = "command line"
18 | test "$(CVAR)" = "c val=spac\ed"
19 | test "$(origin EVAR)" = "environment"
20 | test "$(EVAR)" = "eval"
21 | test "$(OVAL)" = "cline"
22 | test "$(OVAL2)" = "cline2"
23 | test "$(ALLVAR)" = "allspecific"
24 | @echo TEST-PASS
25 |
--------------------------------------------------------------------------------
/tests/submake.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['CVAR=c val=spac\\ed', 'OVAL=cline', 'OVAL2=cline2']
2 |
3 | export EVAR = eval
4 | override OVAL = makefile
5 |
6 | # exporting an override variable doesn't mean it's an override variable
7 | override OVAL2 = makefile2
8 | export OVAL2
9 |
10 | export ALLVAR
11 | ALLVAR = general
12 | all: ALLVAR = allspecific
13 |
14 | all:
15 | test "$(MAKELEVEL)" = "0"
16 | $(MAKE) -f $(TESTPATH)/submake.makefile2
17 |
--------------------------------------------------------------------------------
/tests/subprocess-path.mk:
--------------------------------------------------------------------------------
1 | #T gmake skip
2 | #T grep-for: "2f7cdd0b-7277-48c1-beaf-56cb0dbacb24"
3 |
4 | ifdef __WIN32__
5 | PS:=;
6 | else
7 | PS:=:
8 | endif
9 |
10 | export PATH := $(TESTPATH)/pathdir$(PS)$(PATH)
11 |
12 | # Test two commands. The first one shouldn't go through the shell and the
13 | # second one should. The pathdir subdirectory has a Windows executable called
14 | # pathtest.exe and a shell script called pathtest. We don't care which one is
15 | # run, just that one of the two is (we use a uuid + grep-for to make sure
16 | # that happens).
17 | #
18 | # FAQ:
19 | # Q. Why skip GNU Make?
20 | # A. Because $(TESTPATH) is a Windows-style path, and MSYS make doesn't take
21 | # too kindly to Windows paths in the PATH environment variable.
22 | #
23 | # Q. Why use an exe and not a batch file?
24 | # A. The use cases here were all exe files without the extension. Batch file
25 | # lookup has broken semantics if the .bat extension isn't passed.
26 | #
27 | # Q. Why are the commands silent?
28 | # A. So that we don't pass the grep-for test by mistake.
29 | all:
30 | @pathtest
31 | @pathtest | grep -q 2f7cdd0b-7277-48c1-beaf-56cb0dbacb24
32 | @echo TEST-PASS
33 |
--------------------------------------------------------------------------------
/tests/tab-intro.mk:
--------------------------------------------------------------------------------
1 | # Initial tab characters should be treated well.
2 |
3 | THIS = a value
4 |
5 | ifdef THIS
6 | VAR = conditional value
7 | endif
8 |
9 | all:
10 | test "$(THIS)" = "another value"
11 | test "$(VAR)" = "conditional value"
12 | @echo TEST-PASS
13 |
14 | THAT = makefile syntax
15 |
16 | THIS = another value
17 |
--------------------------------------------------------------------------------
/tests/target-specific.mk:
--------------------------------------------------------------------------------
1 | TESTVAR = anonval
2 |
3 | all: target.suffix target.suffix2 dummy host_test.py my.test1 my.test2
4 | @echo TEST-PASS
5 |
6 | target.suffix: TESTVAR = testval
7 |
8 | %.suffix:
9 | test "$(TESTVAR)" = "testval"
10 |
11 | %.suffix2: TESTVAR = testval2
12 |
13 | %.suffix2:
14 | test "$(TESTVAR)" = "testval2"
15 |
16 | %my: TESTVAR = dummyval
17 |
18 | dummy:
19 | test "$(TESTVAR)" = "dummyval"
20 |
21 | %.py: TESTVAR = pyval
22 | host_%.py: TESTVAR = hostval
23 |
24 | host_test.py:
25 | test "$(TESTVAR)" = "hostval"
26 |
27 | %.test1 %.test2: TESTVAR = %val
28 |
29 | my.test1 my.test2:
30 | test "$(TESTVAR)" = "%val"
31 |
--------------------------------------------------------------------------------
/tests/unexport.mk:
--------------------------------------------------------------------------------
1 | #T environment: {'ENVVAR': 'envval'}
2 |
3 | VAR1 = val1
4 | VAR2 = val2
5 | VAR3 = val3
6 |
7 | unexport VAR3
8 | export VAR1 VAR2 VAR3
9 | unexport VAR2 ENVVAR
10 | unexport
11 |
12 | all:
13 | test "$(ENVVAR)" = "envval" # unexport.mk
14 | $(MAKE) -f $(TESTPATH)/unexport.submk
15 | @echo TEST-PASS
16 |
--------------------------------------------------------------------------------
/tests/unexport.submk:
--------------------------------------------------------------------------------
1 | # -@- Mode: Makefile -@-
2 |
3 | unexport VAR1
4 |
5 | all:
6 | env
7 | test "$(VAR1)" = "val1"
8 | test "$(origin VAR1)" = "environment"
9 | test "$(VAR2)" = "" # VAR2
10 | test "$(VAR3)" = "val3"
11 | test "$(ENVVAR)" = ""
12 | $(MAKE) -f $(TESTPATH)/unexport.submk subt
13 |
14 | subt:
15 | test "$(VAR1)" = ""
16 |
--------------------------------------------------------------------------------
/tests/unterminated-dollar.mk:
--------------------------------------------------------------------------------
1 | VAR = value$
2 | VAR2 = other
3 |
4 | all:
5 | test "$(VAR)" = "value"
6 | @echo TEST-PASS
7 |
--------------------------------------------------------------------------------
/tests/var-change-flavor.mk:
--------------------------------------------------------------------------------
1 | VAR = value1
2 | VAR := value2
3 |
4 | VAR2 := val1
5 | VAR2 = val2
6 |
7 | default:
8 | test "$(flavor VAR)" = "simple"
9 | test "$(VAR)" = "value2"
10 | test "$(flavor VAR2)" = "recursive"
11 | test "$(VAR2)" = "val2"
12 | @echo "TEST-PASS"
13 |
--------------------------------------------------------------------------------
/tests/var-commandline.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['TESTVAR=$(MAKEVAL)', 'TESTVAR2:=$(MAKEVAL)']
2 |
3 | MAKEVAL=testvalue
4 |
5 | all:
6 | test "$(TESTVAR)" = "testvalue"
7 | test "$(TESTVAR2)" = ""
8 | @echo "TEST-PASS"
--------------------------------------------------------------------------------
/tests/var-overrides.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['CLINEVAR=clineval', 'CLINEVAR2=clineval2']
2 |
3 | # this doesn't actually test overrides yet, because they aren't implemented in pymake,
4 | # but testing origins in general is important
5 |
6 | MVAR = mval
7 | CLINEVAR = deadbeef
8 |
9 | override CLINEVAR2 = mval2
10 |
11 | all:
12 | test "$(origin NOVAR)" = "undefined"
13 | test "$(CLINEVAR)" = "clineval"
14 | test "$(origin CLINEVAR)" = "command line"
15 | test "$(MVAR)" = "mval"
16 | test "$(origin MVAR)" = "file"
17 | test "$(@)" = "all"
18 | test "$(origin @)" = "automatic"
19 | test "$(origin CLINEVAR2)" = "override"
20 | test "$(CLINEVAR2)" = "mval2"
21 | @echo TEST-PASS
22 |
--------------------------------------------------------------------------------
/tests/var-ref.mk:
--------------------------------------------------------------------------------
1 | VAR = value
2 | VAR2 == value
3 |
4 | VAR5 = $(NULL) $(NULL)
5 | VARC = value # comment
6 |
7 | $(VAR3)
8 | $(VAR4)
9 | $(VAR5)
10 |
11 | VAR6$(VAR5) = val6
12 |
13 | all:
14 | test "$( VAR)" = ""
15 | test "$(VAR2)" = "= value"
16 | test "${VAR2}" = "= value"
17 | test "$(VAR6 )" = "val6"
18 | test "$(VARC)" = "value "
19 | @echo TEST-PASS
20 |
--------------------------------------------------------------------------------
/tests/var-set.mk:
--------------------------------------------------------------------------------
1 | #T commandline: ['OBASIC=oval']
2 |
3 | BASIC = val
4 |
5 | TEST = $(TEST)
6 |
7 | TEST2 = $(TES
8 | TEST2 += T)
9 |
10 | TES T = val
11 |
12 | RECVAR = foo
13 | RECVAR += var baz
14 |
15 | IMMVAR := bloo
16 | IMMVAR += $(RECVAR)
17 |
18 | BASIC ?= notval
19 |
20 | all: BASIC = valall
21 | all: RECVAR += $(BASIC)
22 | all: IMMVAR += $(BASIC)
23 | all: UNSET += more
24 | all: OBASIC += allmore
25 |
26 | CHECKLIT = $(NULL) check
27 | all: CHECKLIT += appendliteral
28 |
29 | RECVAR = blimey
30 |
31 | TESTEMPTY = \
32 | $(NULL)
33 |
34 | all: other
35 | test "$(TEST2)" = "val"
36 | test '$(value TEST2)' = '$$(TES T)'
37 | test "$(RECVAR)" = "blimey valall"
38 | test "$(IMMVAR)" = "bloo foo var baz valall"
39 | test "$(UNSET)" = "more"
40 | test "$(OBASIC)" = "oval"
41 | test "$(CHECKLIT)" = " check appendliteral"
42 | test "$(TESTEMPTY)" = ""
43 | @echo TEST-PASS
44 |
45 | OVAR = oval
46 | OVAR ?= onotval
47 |
48 | other: OVAR ?= ooval
49 | other: LATERVAR ?= lateroverride
50 |
51 | LATERVAR = olater
52 |
53 | other:
54 | test "$(OVAR)" = "oval"
55 | test "$(LATERVAR)" = "lateroverride"
56 |
--------------------------------------------------------------------------------
/tests/var-substitutions.mk:
--------------------------------------------------------------------------------
1 | SIMPLEVAR = aabb.cc
2 | SIMPLEPERCENT = test_value%extra
3 |
4 | SIMPLE3SUBSTNAME = SIMPLEVAR:.dd
5 | $(SIMPLE3SUBSTNAME) = weirdval
6 |
7 | PERCENT = dummy
8 |
9 | SIMPLESUBST = $(SIMPLEVAR:.cc=.dd)
10 | SIMPLE2SUBST = $(SIMPLEVAR:.cc)
11 | SIMPLE3SUBST = $(SIMPLEVAR:.dd)
12 | SIMPLE4SUBST = $(SIMPLEVAR:.cc=.dd=.ee)
13 | SIMPLE5SUBST = $(SIMPLEVAR:.cc=%.dd)
14 | PERCENTSUBST = $(SIMPLEVAR:%.cc=%.ee)
15 | PERCENT2SUBST = $(SIMPLEVAR:aa%.cc=ff%.f)
16 | PERCENT3SUBST = $(SIMPLEVAR:aa%.dd=gg%.gg)
17 | PERCENT4SUBST = $(SIMPLEVAR:aa%.cc=gg)
18 | PERCENT5SUBST = $(SIMPLEVAR:aa)
19 | PERCENT6SUBST = $(SIMPLEVAR:%.cc=%.dd=%.ee)
20 | PERCENT7SUBST = $(SIMPLEVAR:$(PERCENT).cc=%.dd)
21 | PERCENT8SUBST = $(SIMPLEVAR:%.cc=$(PERCENT).dd)
22 | PERCENT9SUBST = $(SIMPLEVAR:$(PERCENT).cc=$(PERCENT).dd)
23 | PERCENT10SUBST = $(SIMPLEVAR:%%.bb.cc=zz.bb.cc)
24 | PERCENT11SUBST = $(SIMPLEPERCENT:test%value%extra=other%value%extra)
25 |
26 | SPACEDVAR = $(NULL) ex1.c ex2.c $(NULL)
27 | SPACEDSUBST = $(SPACEDVAR:.c=.o)
28 |
29 | all:
30 | test "$(SIMPLESUBST)" = "aabb.dd"
31 | test "$(SIMPLE2SUBST)" = ""
32 | test "$(SIMPLE3SUBST)" = "weirdval"
33 | test "$(SIMPLE4SUBST)" = "aabb.dd=.ee"
34 | test "$(SIMPLE5SUBST)" = "aabb%.dd"
35 | test "$(PERCENTSUBST)" = "aabb.ee"
36 | test "$(PERCENT2SUBST)" = "ffbb.f"
37 | test "$(PERCENT3SUBST)" = "aabb.cc"
38 | test "$(PERCENT4SUBST)" = "gg"
39 | test "$(PERCENT5SUBST)" = ""
40 | test "$(PERCENT6SUBST)" = "aabb.dd=%.ee"
41 | test "$(PERCENT7SUBST)" = "aabb.dd"
42 | test "$(PERCENT8SUBST)" = "aabb.dd"
43 | test "$(PERCENT9SUBST)" = "aabb.dd"
44 | test "$(PERCENT10SUBST)" = "aabb.cc"
45 | test "$(PERCENT11SUBST)" = "other_value%extra"
46 | test "$(SPACEDSUBST)" = "ex1.o ex2.o"
47 | @echo TEST-PASS
48 |
49 | PERCENT = %
50 |
--------------------------------------------------------------------------------
/tests/vpath-directive-dynamic.mk:
--------------------------------------------------------------------------------
1 | $(shell \
2 | mkdir subd1; \
3 | touch subd1/test.in; \
4 | )
5 |
6 | VVAR = %.in subd1
7 |
8 | vpath $(VVAR)
9 |
10 | all: test.in
11 | test "$<" = "subd1/test.in"
12 | @echo TEST-PASS
13 |
--------------------------------------------------------------------------------
/tests/vpath-directive.mk:
--------------------------------------------------------------------------------
1 | # On Windows, MSYS make takes Unix paths but Pymake takes Windows paths
2 | VPSEP := $(if $(and $(__WIN32__),$(.PYMAKE)),;,:)
3 |
4 | $(shell \
5 | mkdir subd1 subd2 subd3; \
6 | printf "reallybaddata" >subd1/foo.in; \
7 | printf "gooddata" >subd2/foo.in; \
8 | printf "baddata" >subd3/foo.in; \
9 | touch subd1/foo.in2 subd2/foo.in2 subd3/foo.in2; \
10 | )
11 |
12 | vpath %.in subd
13 |
14 | vpath
15 | vpath %.in subd2$(VPSEP)subd3
16 |
17 | vpath %.in2 subd0
18 | vpath f%.in2 subd1
19 | vpath %.in2 $(VPSEP)subd2
20 |
21 | %.out: %.in
22 | test "$<" = "subd2/foo.in"
23 | cp $< $@
24 |
25 | %.out2: %.in2
26 | test "$<" = "subd1/foo.in2"
27 | cp $< $@
28 |
29 | all: foo.out foo.out2
30 | test "$$(cat foo.out)" = "gooddata"
31 | @echo TEST-PASS
32 |
--------------------------------------------------------------------------------
/tests/vpath.mk:
--------------------------------------------------------------------------------
1 | VPATH = foo bar
2 |
3 | $(shell \
4 | mkdir foo; touch foo/tfile1; \
5 | mkdir bar; touch bar/tfile2 bar/tfile3 bar/test.objtest; \
6 | sleep 2; \
7 | touch bar/test.source; \
8 | )
9 |
10 | all: tfile1 tfile2 tfile3 test.objtest test.source
11 | test "$^" = "foo/tfile1 bar/tfile2 tfile3 test.objtest bar/test.source"
12 | @echo TEST-PASS
13 |
14 | tfile3: test.objtest
15 |
16 | %.objtest: %.source
17 | test "$<" = bar/test.source
18 | test "$@" = test.objtest
19 |
--------------------------------------------------------------------------------
/tests/vpath2.mk:
--------------------------------------------------------------------------------
1 | VPATH = foo bar
2 |
3 | $(shell \
4 | mkdir bar; touch bar/test.source; \
5 | sleep 2; \
6 | mkdir foo; touch foo/tfile1; \
7 | touch bar/tfile2 bar/tfile3 bar/test.objtest; \
8 | )
9 |
10 | all: tfile1 tfile2 tfile3 test.objtest test.source
11 | test "$^" = "foo/tfile1 bar/tfile2 bar/tfile3 bar/test.objtest bar/test.source"
12 | @echo TEST-PASS
13 |
14 | tfile3: test.objtest
15 |
16 | %.objtest: %.source
17 | test "$<" = bar/test.source
18 | test "$@" = test.objtest
19 |
--------------------------------------------------------------------------------
/tests/wildcards.mk:
--------------------------------------------------------------------------------
1 | $(shell \
2 | mkdir foo; \
3 | touch a.c b.c c.out foo/d.c; \
4 | sleep 2; \
5 | touch c.in; \
6 | )
7 |
8 | VPATH = foo
9 |
10 | all: c.out prog
11 | cat $<
12 | test "$$(cat $<)" = "remadec.out"
13 | @echo TEST-PASS
14 |
15 | *.out: %.out: %.in
16 | test "$@" = c.out
17 | test "$<" = c.in
18 | printf "remade$@" >$@
19 |
20 | prog: *.c
21 | test "$^" = "a.c b.c"
22 | touch $@
23 |
--------------------------------------------------------------------------------
/tests/windows-paths.mk:
--------------------------------------------------------------------------------
1 | all:
2 | touch file.in
3 | printf "%s: %s\n\ttrue" '$(CURDIR)/file.out' '$(CURDIR)/file.in' >test.mk
4 | $(MAKE) -f test.mk $(CURDIR)/file.out
5 | @echo TEST-PASS
6 |
--------------------------------------------------------------------------------