-
Notifications
You must be signed in to change notification settings - Fork 178
Expand file tree
/
Copy pathBuildConfiguration.py
More file actions
696 lines (614 loc) · 28.8 KB
/
BuildConfiguration.py
File metadata and controls
696 lines (614 loc) · 28.8 KB
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
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
import json
import platform
import random
import humanfriendly
from packaging.version import Version, InvalidVersion
from .DockerUtils import DockerUtils
from .WindowsUtils import WindowsUtils
# The default Unreal Engine git repository
DEFAULT_GIT_REPO = "https://github.com/EpicGames/UnrealEngine.git"
# The base images for Linux containers
LINUX_BASE_IMAGES = {
"opengl": "nvidia/opengl:1.0-glvnd-devel-{ubuntu}",
"cuda": "nvidia/cuda:{cuda}-devel-{ubuntu}",
}
# The default ubuntu base to use
DEFAULT_LINUX_VERSION = "ubuntu18.04"
# The default CUDA version to use when `--cuda` is specified without a value
DEFAULT_CUDA_VERSION = "11.4"
# The default memory limit (in GB) under Windows
DEFAULT_MEMORY_LIMIT = 10.0
# The Perforce changelist numbers for each supported .0 release of the Unreal Engine
UNREAL_ENGINE_RELEASE_CHANGELISTS = {
"4.20.0": 4212847,
"4.21.0": 4541578,
"4.22.0": 5660361,
"4.23.0": 8386587,
"4.24.0": 10570792,
"4.25.0": 13144385,
"4.26.0": 14830424,
"4.27.0": 17155196,
"5.0.0": 19505902,
"5.1.0": 23058290,
"5.2.0": 25360045,
}
class VisualStudio(object):
VS2017 = "2017"
VS2019 = "2019"
VS2022 = "2022"
BuildNumbers = {
VS2017: "15",
VS2019: "16",
VS2022: "17",
}
SupportedSince = {
# We do not support versions older than 4.20
VS2017: Version("4.20"),
# Unreal Engine 4.23.1 is the first that successfully builds with Visual Studio v16.3
# See https://github.com/EpicGames/UnrealEngine/commit/2510d4fd07a35ba5bff6ac2c7becaa6e8b7f11fa
#
# Unreal Engine 4.25 is the first that works with .NET SDK 4.7+
# See https://github.com/EpicGames/UnrealEngine/commit/5256eedbdef30212ab69fdf4c09e898098959683
VS2019: Version("4.25"),
VS2022: Version("5.0.0"),
}
UnsupportedSince = {VS2017: Version("5.0")}
class ExcludedComponent(object):
"""
The different components that we support excluding from the built images
"""
# Engine Derived Data Cache (DDC)
DDC = "ddc"
# Engine debug symbols
Debug = "debug"
# Template projects and samples
Templates = "templates"
@staticmethod
def description(component):
"""
Returns a human-readable description of the specified component
"""
return {
ExcludedComponent.DDC: "Derived Data Cache (DDC)",
ExcludedComponent.Debug: "Debug symbols",
ExcludedComponent.Templates: "Template projects and samples",
}.get(component, "[Unknown component]")
class BuildConfiguration(object):
@staticmethod
def addArguments(parser):
"""
Registers our supported command-line arguments with the supplied argument parser
"""
parser.add_argument(
"release",
nargs="?", # aka "required = False", but that doesn't work in positionals
help='UE4 release to build, in semver format (e.g. 4.20.0) or "custom" for a custom repo and branch (deprecated, use --ue-version instead)',
)
parser.add_argument(
"--ue-version",
default=None,
help='UE4 release to build, in semver format (e.g. 4.20.0) or "custom" for a custom repo and branch',
)
parser.add_argument(
"--linux",
action="store_true",
help="Build Linux container images under Windows",
)
parser.add_argument(
"--rebuild",
action="store_true",
help="Rebuild images even if they already exist",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Print `docker build` commands instead of running them",
)
parser.add_argument(
"--no-cache", action="store_true", help="Disable Docker build cache"
)
parser.add_argument(
"--target",
action="append",
default=[],
help="Add a target to the build list. Valid targets are `build-prerequisites`, `source`, `engine`, `minimal`, `full`, and `all`. May be specified multiple times or comma-separated. Defaults to `minimal`.",
)
parser.add_argument(
"--random-memory",
action="store_true",
help="Use a random memory limit for Windows containers",
)
parser.add_argument(
"--exclude",
action="append",
default=[],
choices=[
ExcludedComponent.DDC,
ExcludedComponent.Debug,
ExcludedComponent.Templates,
],
help="Exclude the specified component (can be specified multiple times to exclude multiple components)",
)
parser.add_argument(
"--opt",
action="append",
default=[],
help="Set an advanced configuration option (can be specified multiple times to specify multiple options)",
)
parser.add_argument(
"--cuda",
default=None,
metavar="VERSION",
help="Add CUDA support as well as OpenGL support when building Linux containers",
)
parser.add_argument(
"--visual-studio",
default=VisualStudio.VS2017,
choices=VisualStudio.BuildNumbers.keys(),
help="Specify Visual Studio Build Tools version to use for Windows containers",
)
parser.add_argument(
"-username",
default=None,
help="Specify the username to use when cloning the git repository",
)
parser.add_argument(
"-password",
default=None,
help="Specify access token or password to use when cloning the git repository",
)
parser.add_argument(
"-repo",
default=None,
help='Set the custom git repository to clone when "custom" is specified as the release value',
)
parser.add_argument(
"-branch",
default=None,
help='Set the custom branch/tag to clone when "custom" is specified as the release value',
)
parser.add_argument(
"-isolation",
default=None,
help="Set the isolation mode to use for Windows containers (process or hyperv)",
)
parser.add_argument(
"-basetag",
default=None if platform.system() == "Windows" else DEFAULT_LINUX_VERSION,
help="Operating system base image tag to use. For Linux this is the version of Ubuntu (default is ubuntu18.04). "
"For Windows this is the Windows Server Core base image tag (default is the host OS version)",
)
parser.add_argument(
"-suffix", default="", help="Add a suffix to the tags of the built images"
)
parser.add_argument(
"-m",
default=None,
help="Override the default memory limit under Windows (also overrides --random-memory)",
)
parser.add_argument(
"-ue4cli",
default=None,
help="Override the default version of ue4cli installed in the ue4-full image",
)
parser.add_argument(
"-conan-ue4cli",
default=None,
help="Override the default version of conan-ue4cli installed in the ue4-full image",
)
parser.add_argument(
"-layout",
default=None,
help="Copy generated Dockerfiles to the specified directory and don't build the images",
)
parser.add_argument(
"--combine",
action="store_true",
help="Combine generated Dockerfiles into a single multi-stage build Dockerfile",
)
parser.add_argument(
"--monitor",
action="store_true",
help="Monitor resource usage during builds (useful for debugging)",
)
parser.add_argument(
"-interval",
type=float,
default=20.0,
help="Sampling interval in seconds when resource monitoring has been enabled using --monitor (default is 20 seconds)",
)
parser.add_argument(
"--ignore-blacklist",
action="store_true",
help="Run builds even on blacklisted versions of Windows (advanced use only)",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Enable verbose output during builds (useful for debugging)",
)
parser.add_argument(
"-changelist",
type=int,
default=None,
help="Set a specific changelist number in the Unreal Engine's Build.version file",
)
parser.add_argument(
"--prerequisites-dockerfile",
default=None,
help="Specifies path to custom ue4-build-prerequisites dockerfile",
)
def __init__(self, parser, argv, logger):
"""
Creates a new build configuration based on the supplied arguments object
"""
# If the user has specified `--cuda` without a version value, treat the value as an empty string
argv = [arg + "=" if arg == "--cuda" else arg for arg in argv]
# Parse the supplied command-line arguments
self.args = parser.parse_args(argv)
self.changelist = self.args.changelist
if len(self.args.target) <= 0:
self.args.target = ["minimal"]
# these can be token-delimited, so let's just split them apart and then remerge them into one list
split_target = [item.split(",") for item in self.args.target]
self.args.target = [item for sublist in split_target for item in sublist]
# Now that we have our options in `self.args.target`, evaluate our dependencies
# In a theoretical ideal world this should be code-driven; if you find yourself adding a lot more code to this, consider a redesign!
active_targets = set(self.args.target)
# build-prereq -> source -> engine
# build-prereq -> source -> minimal -> full
# We initialize these with all the options, with the intent that you should be accessing them directly and not checking for existence
# This is to avoid typos giving false-negatives; KeyError is reliable and tells you what you did wrong
self.buildTargets = {
"build-prerequisites": False,
"source": False,
"engine": False,
"minimal": False,
"full": False,
}
for target in active_targets:
if target != "all" and target not in self.buildTargets:
valid_options = sorted(self.buildTargets.keys())
raise RuntimeError(
f"unknown build target '{target}', valid options are: all {' '.join(valid_options)}"
)
if "full" in active_targets or "all" in active_targets:
self.buildTargets["full"] = True
active_targets.add("minimal")
if "minimal" in active_targets or "all" in active_targets:
self.buildTargets["minimal"] = True
active_targets.add("source")
if "engine" in active_targets or "all" in active_targets:
self.buildTargets["engine"] = True
active_targets.add("source")
if "source" in active_targets or "all" in active_targets:
self.buildTargets["source"] = True
active_targets.add("build-prerequisites")
if "build-prerequisites" in active_targets or "all" in active_targets:
self.buildTargets["build-prerequisites"] = True
if not self.buildTargets["build-prerequisites"]:
raise RuntimeError(
"we're not building anything; this shouldn't even be possible, but is definitely not useful"
)
# See if the user specified both the old positional version option and the new ue-version option
if self.args.release is not None and self.args.ue_version is not None:
raise RuntimeError(
"specified both `--ue-version` and the old positional version option; please use only `--ue-version`!"
)
# For the sake of a simpler pull request, we use self.args.release as the canonical place for this data.
# If support for the old positional version option is removed, this should be fixed.
if self.args.ue_version is not None:
self.args.release = self.args.ue_version
# We care about the version number only if we're building source
if self.buildTargets["source"]:
if self.args.release is None:
raise RuntimeError("missing `--ue-version` when building source")
# Determine if we are building a custom version of UE4 rather than an official release
self.args.release = self.args.release.lower()
if self.args.release == "custom" or self.args.release.startswith("custom:"):
# Both a custom repository and a custom branch/tag must be specified
if self.args.repo is None or self.args.branch is None:
raise RuntimeError(
"both a repository and branch/tag must be specified when building a custom version of the Engine"
)
# Use the specified repository and branch/tag
customName = (
self.args.release.split(":", 2)[1].strip()
if ":" in self.args.release
else ""
)
self.release = customName if len(customName) > 0 else "custom"
self.repository = self.args.repo
self.branch = self.args.branch
self.custom = True
else:
# Validate the specified version string
try:
ue4Version = Version(self.args.release)
if ue4Version.major not in [4, 5] or ue4Version.pre is not None:
raise Exception(f"unsupported engine version: {ue4Version}")
self.release = (
f"{ue4Version.major}.{ue4Version.minor}.{ue4Version.micro}"
)
except InvalidVersion:
raise RuntimeError(
'invalid Unreal Engine release number "{}", full semver format required (e.g. "4.20.0")'.format(
self.args.release
)
)
# Use the default repository and the release tag for the specified version
self.repository = DEFAULT_GIT_REPO
self.branch = "{}-release".format(self.release)
self.custom = False
# If the user specified a .0 release of the Unreal Engine and did not specify a changelist override then
# use the official changelist number for that release to ensure consistency with Epic Games Launcher builds
# (This is necessary because .0 releases do not include a `CompatibleChangelist` value in Build.version)
if (
self.changelist is None
and self.release in UNREAL_ENGINE_RELEASE_CHANGELISTS
):
self.changelist = UNREAL_ENGINE_RELEASE_CHANGELISTS[self.release]
else:
# defaults needed by other parts of the codebase
self.custom = False
self.release = None
self.repository = None
self.branch = None
# Store our common configuration settings
self.containerPlatform = (
"windows"
if platform.system() == "Windows" and self.args.linux == False
else "linux"
)
self.dryRun = self.args.dry_run
self.rebuild = self.args.rebuild
self.suffix = self.args.suffix
self.platformArgs = ["--no-cache"] if self.args.no_cache == True else []
self.excludedComponents = set(self.args.exclude)
self.baseImage = None
self.prereqsTag = None
self.ignoreBlacklist = self.args.ignore_blacklist
self.verbose = self.args.verbose
self.layoutDir = self.args.layout
self.combine = self.args.combine
# If the user specified custom version strings for ue4cli and/or conan-ue4cli, process them
self.ue4cliVersion = self._processPackageVersion("ue4cli", self.args.ue4cli)
self.conanUe4cliVersion = self._processPackageVersion(
"conan-ue4cli", self.args.conan_ue4cli
)
# Process any specified advanced configuration options (which we use directly as context values for the Jinja templating system)
self.opts = {}
for o in self.args.opt:
if "=" in o:
key, value = o.split("=", 1)
self.opts[key.replace("-", "_")] = self._processTemplateValue(value)
else:
self.opts[o.replace("-", "_")] = True
# If we are generating Dockerfiles then generate them for all images that have not been explicitly excluded
if self.layoutDir is not None:
self.rebuild = True
# If we are generating Dockerfiles and combining them then set the corresponding Jinja context value
if self.layoutDir is not None and self.combine == True:
self.opts["combine"] = True
# If the user requested an option that is only compatible with generated Dockerfiles then ensure `-layout` was specified
if self.layoutDir is None and self.opts.get("source_mode", "git") != "git":
raise RuntimeError(
"the `-layout` flag must be used when specifying a non-default value for the `source_mode` option"
)
if self.layoutDir is None and self.combine == True:
raise RuntimeError(
"the `-layout` flag must be used when specifying the `--combine` flag"
)
# We care about source_mode and credential_mode only if we're building source
if self.buildTargets["source"]:
# Verify that the value for `source_mode` is valid if specified
validSourceModes = ["git", "copy"]
if self.opts.get("source_mode", "git") not in validSourceModes:
raise RuntimeError(
"invalid value specified for the `source_mode` option, valid values are {}".format(
validSourceModes
)
)
if "credential_mode" not in self.opts:
# On Linux, default to secrets mode that causes fewer issues with firewalls
self.opts["credential_mode"] = (
"secrets" if self.containerPlatform == "linux" else "endpoint"
)
# Verify that the value for `credential_mode` is valid if specified
validCredentialModes = (
["endpoint", "secrets"]
if self.containerPlatform == "linux"
else ["endpoint"]
)
if self.opts["credential_mode"] not in validCredentialModes:
raise RuntimeError(
"invalid value specified for the `credential_mode` option, valid values are {} when building {} containers".format(
validCredentialModes, self.containerPlatform.title()
)
)
# Generate Jinja context values for keeping or excluding components
self.opts["excluded_components"] = {
"ddc": ExcludedComponent.DDC in self.excludedComponents,
"debug": ExcludedComponent.Debug in self.excludedComponents,
"templates": ExcludedComponent.Templates in self.excludedComponents,
}
if "gitdependencies_args" not in self.opts:
self.opts["gitdependencies_args"] = (
"--exclude=Android --exclude=Mac --exclude=Linux"
if self.containerPlatform == "windows"
else "--exclude=Android --exclude=Mac --exclude=Win32 --exclude=Win64"
)
# Warn user that they are in danger of Docker 20GB COPY bug
# Unfortunately, we don't have a cheap way to check whether user environment is affected
# See https://github.com/adamrehn/ue4-docker/issues/99
if self.containerPlatform == "windows":
warn20GiB = False
if ExcludedComponent.Debug not in self.excludedComponents:
logger.warning("Warning: You didn't pass --exclude debug", False)
warn20GiB = True
if self.release and not self.custom and Version(self.release).major >= 5:
logger.warning("Warning: You're building Unreal Engine 5", False)
warn20GiB = True
if warn20GiB:
logger.warning("Warning: You might hit Docker 20GiB COPY bug", False)
logger.warning(
"Warning: Make sure that `ue4-docker diagnostics 20gig` passes",
False,
)
logger.warning(
"Warning: See https://github.com/adamrehn/ue4-docker/issues/99#issuecomment-1079702817 for details and workarounds",
False,
)
# If we're building Windows containers, generate our Windows-specific configuration settings
if self.containerPlatform == "windows":
self._generateWindowsConfig()
# If we're building Linux containers, generate our Linux-specific configuration settings
if self.containerPlatform == "linux":
self._generateLinuxConfig()
# If the user-specified suffix passed validation, prefix it with a dash
self.suffix = "-{}".format(self.suffix) if self.suffix != "" else ""
def describeExcludedComponents(self):
"""
Returns a list of strings describing the components that will be excluded (if any.)
"""
return sorted(
[
ExcludedComponent.description(component)
for component in self.excludedComponents
]
)
def _generateWindowsConfig(self):
self.visualStudio = self.args.visual_studio
if self.release is not None and not self.custom:
# Check whether specified Unreal Engine release is compatible with specified Visual Studio
supportedSince = VisualStudio.SupportedSince.get(self.visualStudio, None)
if supportedSince is not None and Version(self.release) < supportedSince:
raise RuntimeError(
"specified version of Unreal Engine is too old for Visual Studio {}".format(
self.visualStudio
)
)
unsupportedSince = VisualStudio.UnsupportedSince.get(
self.visualStudio, None
)
if (
unsupportedSince is not None
and Version(self.release) >= unsupportedSince
):
raise RuntimeError(
"Visual Studio {} is too old for specified version of Unreal Engine".format(
self.visualStudio
)
)
self.visualStudioBuildNumber = VisualStudio.BuildNumbers[self.visualStudio]
# See https://github.com/EpicGames/UnrealEngine/commit/72585138472785e2ee58aab9950a7260275ee2ac
# Note: We must not pass VS2019 arg for older UE4 versions that didn't have VS2019 variable in their build graph xml.
# Otherwise, UAT errors out with "Unknown argument: VS2019".
if self.visualStudio != VisualStudio.VS2017:
self.opts["buildgraph_args"] = (
self.opts.get("buildgraph_args", "")
+ f" -set:VS{self.visualStudio}=true"
)
# Determine base tag for the Windows release of the host system
self.hostBasetag = WindowsUtils.getHostBaseTag()
# Store the tag for the base Windows Server Core image
self.basetag = (
self.args.basetag if self.args.basetag is not None else self.hostBasetag
)
if self.basetag is None:
raise RuntimeError(
"unable to determine Windows Server Core base image tag from host system. Specify it explicitly using -basetag command-line flag"
)
self.baseImage = "mcr.microsoft.com/windows/servercore:" + self.basetag
self.dllSrcImage = WindowsUtils.getDllSrcImage(self.basetag)
self.prereqsTag = self.basetag + "-vs" + self.visualStudio
# If the user has explicitly specified an isolation mode then use it, otherwise auto-detect
if self.args.isolation is not None:
self.isolation = self.args.isolation
else:
# If we are able to use process isolation mode then use it, otherwise use Hyper-V isolation mode
differentKernels = self.basetag != self.hostBasetag
dockerSupportsProcess = Version(
DockerUtils.version()["Version"]
) >= Version("18.09.0")
if not differentKernels and dockerSupportsProcess:
self.isolation = "process"
else:
self.isolation = "hyperv"
# Set the isolation mode Docker flag
self.platformArgs.append("--isolation=" + self.isolation)
# If the user has explicitly specified a memory limit then use it, otherwise auto-detect
self.memLimit = None
if self.args.m is not None:
try:
self.memLimit = humanfriendly.parse_size(self.args.m) / (
1000 * 1000 * 1000
)
except:
raise RuntimeError('invalid memory limit "{}"'.format(self.args.m))
else:
# Only specify a memory limit when using Hyper-V isolation mode, in order to override the 1GB default limit
# (Process isolation mode does not impose any memory limits by default)
if self.isolation == "hyperv":
self.memLimit = (
DEFAULT_MEMORY_LIMIT
if self.args.random_memory == False
else random.uniform(
DEFAULT_MEMORY_LIMIT, DEFAULT_MEMORY_LIMIT + 2.0
)
)
# Set the memory limit Docker flag
if self.memLimit is not None:
self.platformArgs.extend(["-m", "{:.2f}GB".format(self.memLimit)])
def _generateLinuxConfig(self):
# Verify that any user-specified tag suffix does not collide with our base tags
if self.suffix.startswith("opengl") or self.suffix.startswith("cuda"):
raise RuntimeError('tag suffix cannot begin with "opengl" or "cuda".')
# Determine if we are building CUDA-enabled container images
self.cuda = None
if self.args.cuda is not None:
# Verify that the specified CUDA version is valid
self.cuda = self.args.cuda if self.args.cuda != "" else DEFAULT_CUDA_VERSION
# Use the appropriate base image for the specified CUDA version
self.baseImage = LINUX_BASE_IMAGES["cuda"]
self.prereqsTag = "cuda{cuda}-{ubuntu}"
else:
self.baseImage = LINUX_BASE_IMAGES["opengl"]
self.prereqsTag = "opengl-{ubuntu}"
self.baseImage = self.baseImage.format(
cuda=self.args.cuda, ubuntu=self.args.basetag
)
self.prereqsTag = self.prereqsTag.format(
cuda=self.args.cuda, ubuntu=self.args.basetag
)
def _processPackageVersion(self, package, version):
# Leave the version value unmodified if a blank version was specified or a fully-qualified version was specified
# (e.g. package==X.X.X, package>=X.X.X, git+https://url/for/package/repo.git, etc.)
if version is None or "/" in version or version.lower().startswith(package):
return version
# If a version specifier (e.g. ==X.X.X, >=X.X.X, etc.) was specified, prefix it with the package name
if "=" in version:
return package + version
# If a raw version number was specified, prefix the package name and a strict equality specifier
return "{}=={}".format(package, version)
def _processTemplateValue(self, value):
# If the value is a boolean (either raw or represented by zero or one) then parse it
if value.lower() in ["true", "1"]:
return True
elif value.lower() in ["false", "0"]:
return False
# If the value is a JSON object or array then attempt to parse it
if (value.startswith("{") and value.endswith("}")) or (
value.startswith("[") and value.endswith("]")
):
try:
return json.loads(value)
except:
print(
'Warning: could not parse option value "{}" as JSON, treating value as a string'.format(
value
)
)
# Treat all other values as strings
return value