-
-
Notifications
You must be signed in to change notification settings - Fork 789
/
piobuild.py
379 lines (307 loc) · 12.3 KB
/
piobuild.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import fnmatch
import os
import sys
from SCons import Builder, Util # pylint: disable=import-error
from SCons.Node import FS # pylint: disable=import-error
from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
from SCons.Script import AlwaysBuild # pylint: disable=import-error
from SCons.Script import DefaultEnvironment # pylint: disable=import-error
from SCons.Script import SConscript # pylint: disable=import-error
from platformio import __version__, fs
from platformio.compat import IS_MACOS, string_types
from platformio.package.version import pepver_to_semver
SRC_HEADER_EXT = ["h", "hpp"]
SRC_ASM_EXT = ["S", "spp", "SPP", "sx", "s", "asm", "ASM"]
SRC_C_EXT = ["c"]
SRC_CXX_EXT = ["cc", "cpp", "cxx", "c++"]
SRC_BUILD_EXT = SRC_C_EXT + SRC_CXX_EXT + SRC_ASM_EXT
SRC_FILTER_DEFAULT = ["+<*>", "-<.git%s>" % os.sep, "-<.svn%s>" % os.sep]
def scons_patched_match_splitext(path, suffixes=None):
"""Patch SCons Builder, append $OBJSUFFIX to the end of each target"""
tokens = Util.splitext(path)
if suffixes and tokens[1] and tokens[1] in suffixes:
return (path, tokens[1])
return tokens
def GetBuildType(env):
modes = []
if (
set(["__debug", "sizedata"]) # sizedata = for memory inspection
& set(COMMAND_LINE_TARGETS)
or env.GetProjectOption("build_type") == "debug"
):
modes.append("debug")
if "__test" in COMMAND_LINE_TARGETS or env.GetProjectOption("build_type") == "test":
modes.append("test")
return "+".join(modes or ["release"])
def BuildProgram(env):
env.ProcessProgramDeps()
env.ProcessProjectDeps()
# append into the beginning a main LD script
if env.get("LDSCRIPT_PATH") and not any("-Wl,-T" in f for f in env["LINKFLAGS"]):
env.Prepend(LINKFLAGS=["-T", env.subst("$LDSCRIPT_PATH")])
# enable "cyclic reference" for linker
if (
env.get("LIBS")
and env.GetCompilerType() == "gcc"
and (env.PioPlatform().is_embedded() or not IS_MACOS)
):
env.Prepend(_LIBFLAGS="-Wl,--start-group ")
env.Append(_LIBFLAGS=" -Wl,--end-group")
program = env.Program(env.subst("$PROGPATH"), env["PIOBUILDFILES"])
env.Replace(PIOMAINPROG=program)
AlwaysBuild(
env.Alias(
"checkprogsize",
program,
env.VerboseAction(env.CheckUploadSize, "Checking size $PIOMAINPROG"),
)
)
print("Building in %s mode" % env["BUILD_TYPE"])
return program
def ProcessProgramDeps(env):
def _append_pio_macros():
core_version = pepver_to_semver(__version__)
env.AppendUnique(
CPPDEFINES=[
(
"PLATFORMIO",
int(
"{0:02d}{1:02d}{2:02d}".format(
core_version.major, core_version.minor, core_version.patch
)
),
)
]
)
_append_pio_macros()
env.PrintConfiguration()
# process extra flags from board
if "BOARD" in env and "build.extra_flags" in env.BoardConfig():
env.ProcessFlags(env.BoardConfig().get("build.extra_flags"))
# apply user flags
env.ProcessFlags(env.get("BUILD_FLAGS"))
# process framework scripts
env.BuildFrameworks(env.get("PIOFRAMEWORK"))
if "debug" in env["BUILD_TYPE"]:
env.ConfigureDebugTarget()
# remove specified flags
env.ProcessUnFlags(env.get("BUILD_UNFLAGS"))
if "compiledb" in COMMAND_LINE_TARGETS and env.get(
"COMPILATIONDB_INCLUDE_TOOLCHAIN"
):
for scope, includes in env.DumpIntegrationIncludes().items():
if scope in ("toolchain",):
env.Append(CPPPATH=includes)
def ProcessProjectDeps(env):
plb = env.ConfigureProjectLibBuilder()
# prepend project libs to the beginning of list
env.Prepend(LIBS=plb.build())
# prepend extra linker related options from libs
env.PrependUnique(
**{
key: plb.env.get(key)
for key in ("LIBS", "LIBPATH", "LINKFLAGS")
if plb.env.get(key)
}
)
if "test" in env["BUILD_TYPE"]:
build_files_before_nums = len(env.get("PIOBUILDFILES", []))
plb.env.BuildSources(
"$BUILD_TEST_DIR", "$PROJECT_TEST_DIR", "$PIOTEST_SRC_FILTER"
)
if len(env.get("PIOBUILDFILES", [])) - build_files_before_nums < 1:
sys.stderr.write(
"Error: Nothing to build. Please put your test suites "
"to the '%s' folder\n" % env.subst("$PROJECT_TEST_DIR")
)
env.Exit(1)
if "test" not in env["BUILD_TYPE"] or env.GetProjectOption("test_build_src"):
plb.env.BuildSources(
"$BUILD_SRC_DIR", "$PROJECT_SRC_DIR", env.get("SRC_FILTER")
)
if not env.get("PIOBUILDFILES") and not COMMAND_LINE_TARGETS:
sys.stderr.write(
"Error: Nothing to build. Please put your source code files "
"to the '%s' folder\n" % env.subst("$PROJECT_SRC_DIR")
)
env.Exit(1)
def ParseFlagsExtended(env, flags): # pylint: disable=too-many-branches
if not isinstance(flags, list):
flags = [flags]
result = {}
for raw in flags:
for key, value in env.ParseFlags(str(raw)).items():
if key not in result:
result[key] = []
result[key].extend(value)
cppdefines = []
for item in result["CPPDEFINES"]:
if not Util.is_Sequence(item):
cppdefines.append(item)
continue
name, value = item[:2]
if '"' in value:
value = value.replace('"', '\\"')
elif value.isdigit():
value = int(value)
elif value.replace(".", "", 1).isdigit():
value = float(value)
cppdefines.append((name, value))
result["CPPDEFINES"] = cppdefines
# fix relative CPPPATH & LIBPATH
for k in ("CPPPATH", "LIBPATH"):
for i, p in enumerate(result.get(k, [])):
if os.path.isdir(p):
result[k][i] = os.path.abspath(p)
# fix relative path for "-include"
for i, f in enumerate(result.get("CCFLAGS", [])):
if isinstance(f, tuple) and f[0] == "-include":
result["CCFLAGS"][i] = (f[0], env.File(os.path.abspath(f[1].get_path())))
return result
def ProcessFlags(env, flags): # pylint: disable=too-many-branches
if not flags:
return
env.Append(**env.ParseFlagsExtended(flags))
# Cancel any previous definition of name, either built in or
# provided with a -U option // Issue #191
undefines = [
u
for u in env.get("CCFLAGS", [])
if isinstance(u, string_types) and u.startswith("-U")
]
if undefines:
for undef in undefines:
env["CCFLAGS"].remove(undef)
if undef[2:] in env["CPPDEFINES"]:
env["CPPDEFINES"].remove(undef[2:])
env.Append(_CPPDEFFLAGS=" %s" % " ".join(undefines))
def ProcessUnFlags(env, flags):
if not flags:
return
parsed = env.ParseFlagsExtended(flags)
unflag_scopes = tuple(set(["ASPPFLAGS"] + list(parsed.keys())))
for scope in unflag_scopes:
for unflags in parsed.values():
for unflag in unflags:
for current in list(env.get(scope, [])):
conditions = [
unflag == current,
not isinstance(unflag, (tuple, list))
and isinstance(current, (tuple, list))
and unflag == current[0],
]
if any(conditions):
env[scope].remove(current)
def StringifyMacro(env, value): # pylint: disable=unused-argument
return '\\"%s\\"' % value.replace('"', '\\\\\\"')
def MatchSourceFiles(env, src_dir, src_filter=None, src_exts=None):
src_filter = env.subst(src_filter) if src_filter else None
src_filter = src_filter or SRC_FILTER_DEFAULT
src_exts = src_exts or (SRC_BUILD_EXT + SRC_HEADER_EXT)
return fs.match_src_files(env.subst(src_dir), src_filter, src_exts)
def CollectBuildFiles(
env, variant_dir, src_dir, src_filter=None, duplicate=False
): # pylint: disable=too-many-locals
sources = []
variants = []
src_dir = env.subst(src_dir)
if src_dir.endswith(os.sep):
src_dir = src_dir[:-1]
for item in env.MatchSourceFiles(src_dir, src_filter, SRC_BUILD_EXT):
_reldir = os.path.dirname(item)
_src_dir = os.path.join(src_dir, _reldir) if _reldir else src_dir
_var_dir = os.path.join(variant_dir, _reldir) if _reldir else variant_dir
if _var_dir not in variants:
variants.append(_var_dir)
env.VariantDir(_var_dir, _src_dir, duplicate)
sources.append(env.File(os.path.join(_var_dir, os.path.basename(item))))
middlewares = env.get("__PIO_BUILD_MIDDLEWARES")
if not middlewares:
return sources
new_sources = []
for node in sources:
new_node = node
for callback, pattern in middlewares:
if pattern and not fnmatch.fnmatch(node.srcnode().get_path(), pattern):
continue
if callback.__code__.co_argcount == 2:
new_node = callback(env, new_node)
else:
new_node = callback(new_node)
if not new_node:
break
if new_node:
new_sources.append(new_node)
return new_sources
def AddBuildMiddleware(env, callback, pattern=None):
env.Append(__PIO_BUILD_MIDDLEWARES=[(callback, pattern)])
def BuildFrameworks(env, frameworks):
if not frameworks:
return
if "BOARD" not in env:
sys.stderr.write(
"Please specify `board` in `platformio.ini` to use "
"with '%s' framework\n" % ", ".join(frameworks)
)
env.Exit(1)
supported_frameworks = env.BoardConfig().get("frameworks", [])
for name in frameworks:
if name == "arduino":
# Arduino IDE appends .o to the end of filename
Builder.match_splitext = scons_patched_match_splitext
if "nobuild" not in COMMAND_LINE_TARGETS:
env.ConvertInoToCpp()
if name in supported_frameworks:
SConscript(env.GetFrameworkScript(name), exports="env")
else:
sys.stderr.write("Error: This board doesn't support %s framework!\n" % name)
env.Exit(1)
def BuildLibrary(env, variant_dir, src_dir, src_filter=None, nodes=None):
env.ProcessUnFlags(env.get("BUILD_UNFLAGS"))
nodes = nodes or env.CollectBuildFiles(variant_dir, src_dir, src_filter)
return env.StaticLibrary(env.subst(variant_dir), nodes)
def BuildSources(env, variant_dir, src_dir, src_filter=None):
if env.get("PIOMAINPROG"):
sys.stderr.write(
"Error: The main program is already constructed and the inline "
"source files are not allowed. Please use `env.BuildLibrary(...)` "
"or PRE-type script instead."
)
env.Exit(1)
nodes = env.CollectBuildFiles(variant_dir, src_dir, src_filter)
DefaultEnvironment().Append(
PIOBUILDFILES=[
env.Object(node) if isinstance(node, FS.File) else node for node in nodes
]
)
def exists(_):
return True
def generate(env):
env.AddMethod(GetBuildType)
env.AddMethod(BuildProgram)
env.AddMethod(ProcessProgramDeps)
env.AddMethod(ProcessProjectDeps)
env.AddMethod(ParseFlagsExtended)
env.AddMethod(ProcessFlags)
env.AddMethod(ProcessUnFlags)
env.AddMethod(StringifyMacro)
env.AddMethod(MatchSourceFiles)
env.AddMethod(CollectBuildFiles)
env.AddMethod(AddBuildMiddleware)
env.AddMethod(BuildFrameworks)
env.AddMethod(BuildLibrary)
env.AddMethod(BuildSources)
return env