Skip to content

Commit 94675e5

Browse files
committed
languages: add python support
1 parent 6b3815e commit 94675e5

File tree

2 files changed

+161
-17
lines changed

2 files changed

+161
-17
lines changed

Diff for: pym/bob/input.py

+18-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from . import BOB_VERSION, BOB_INPUT_HASH, DEBUG
77
from .errors import ParseError, BobError
8-
from .languages import getLanguage, ScriptLanguage, BashLanguage, PwshLanguage
8+
from .languages import getLanguage, ScriptLanguage, BashLanguage, PwshLanguage, PythonLanguage
99
from .pathspec import PackageSet
1010
from .scm import CvsScm, GitScm, ImportScm, SvnScm, UrlScm, ScmOverride, \
1111
auditFromDir, getScm, SYNTHETIC_SCM_PROPS
@@ -115,9 +115,11 @@ def fetchFingerprintScripts(recipe):
115115
recipe.get("fingerprintScript")),
116116
ScriptLanguage.PWSH : recipe.get("fingerprintScriptPwsh",
117117
recipe.get("fingerprintScript")),
118+
ScriptLanguage.PYTHON : recipe.get("fingerprintScriptPython",
119+
recipe.get("fingerprintScript")),
118120
}
119121

120-
def fetchScripts(recipe, prefix, resolveBash, resolvePwsh):
122+
def fetchScripts(recipe, prefix, resolveBash, resolvePwsh, resolvePython):
121123
return {
122124
ScriptLanguage.BASH : (
123125
resolveBash(recipe.get(prefix + "SetupBash", recipe.get(prefix + "Setup")),
@@ -130,7 +132,13 @@ def fetchScripts(recipe, prefix, resolveBash, resolvePwsh):
130132
prefix + "Setup[Pwsh]"),
131133
resolvePwsh(recipe.get(prefix + "ScriptPwsh", recipe.get(prefix + "Script")),
132134
prefix + "Script[Pwsh]"),
133-
)
135+
),
136+
ScriptLanguage.PYTHON : (
137+
resolvePython(recipe.get(prefix + "SetupPython", recipe.get(prefix + "Setup")),
138+
prefix + "Setup[Python]"),
139+
resolvePython(recipe.get(prefix + "ScriptPython", recipe.get(prefix + "Script")),
140+
prefix + "Script[Python]"),
141+
),
134142
}
135143

136144
def mergeScripts(fragments, glue):
@@ -2187,9 +2195,11 @@ def __init__(self, recipeSet, recipe, layer, sourceFile, baseDir, packageName, b
21872195
baseDir, packageName, sourceName).resolve
21882196
incHelperPwsh = IncludeHelper(PwshLanguage, recipeSet.loadBinary,
21892197
baseDir, packageName, sourceName).resolve
2198+
incHelperPython = IncludeHelper(PythonLanguage, recipeSet.loadBinary,
2199+
baseDir, packageName, sourceName).resolve
21902200

21912201
self.__scriptLanguage = recipe.get("scriptLanguage")
2192-
self.__checkout = fetchScripts(recipe, "checkout", incHelperBash, incHelperPwsh)
2202+
self.__checkout = fetchScripts(recipe, "checkout", incHelperBash, incHelperPwsh, incHelperPython)
21932203
self.__checkoutSCMs = recipe.get("checkoutSCM", [])
21942204
for scm in self.__checkoutSCMs:
21952205
scm["__source"] = sourceName
@@ -2199,8 +2209,8 @@ def __init__(self, recipeSet, recipe, layer, sourceFile, baseDir, packageName, b
21992209
for a in self.__checkoutAsserts:
22002210
a["__source"] = sourceName + ", checkoutAssert #{}".format(i)
22012211
i += 1
2202-
self.__build = fetchScripts(recipe, "build", incHelperBash, incHelperPwsh)
2203-
self.__package = fetchScripts(recipe, "package", incHelperBash, incHelperPwsh)
2212+
self.__build = fetchScripts(recipe, "build", incHelperBash, incHelperPwsh, incHelperPython)
2213+
self.__package = fetchScripts(recipe, "package", incHelperBash, incHelperPwsh, incHelperPython)
22042214
self.__fingerprintScriptList = fetchFingerprintScripts(recipe)
22052215
self.__fingerprintIf = recipe.get("fingerprintIf")
22062216
self.__fingerprintVarsList = set(recipe.get("fingerprintVars", []))
@@ -3055,7 +3065,7 @@ class RecipeSet:
30553065
),
30563066
schema.Optional('layers') : [str],
30573067
schema.Optional('scriptLanguage',
3058-
default=ScriptLanguage.BASH) : schema.And(schema.Or("bash", "PowerShell"),
3068+
default=ScriptLanguage.BASH) : schema.And(schema.Or("bash", "PowerShell", "python"),
30593069
schema.Use(ScriptLanguage)),
30603070
})
30613071

@@ -3727,7 +3737,7 @@ def __createSchemas(self):
37273737
schema.Optional('fingerprintScriptPwsh', default="") : str,
37283738
schema.Optional('fingerprintIf') : schema.Or(None, str, bool, IfExpression),
37293739
schema.Optional('fingerprintVars') : [ varNameUseSchema ],
3730-
schema.Optional('scriptLanguage') : schema.And(schema.Or("bash", "PowerShell"),
3740+
schema.Optional('scriptLanguage') : schema.And(schema.Or("bash", "PowerShell", "python"),
37313741
schema.Use(ScriptLanguage)),
37323742
schema.Optional('jobServer') : bool,
37333743
}

Diff for: pym/bob/languages.py

+143-9
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
class ScriptLanguage(Enum):
140140
BASH = 'bash'
141141
PWSH = 'PowerShell'
142+
PYTHON = 'python'
142143

143144

144145
class IncludeResolver:
@@ -185,7 +186,7 @@ def _includeLiteral(self, content):
185186
raise NotImplementedError()
186187

187188
def _resolveContent(self, result):
188-
raise NotImplementedError()
189+
return result
189190

190191
def resolve(self, result):
191192
return (self._resolveContent(result), "\n".join(self.__incDigests), self.__incFiles)
@@ -397,20 +398,12 @@ def setupFingerprint(spec, env):
397398

398399

399400
class PwshResolver(IncludeResolver):
400-
def __init__(self, fileLoader, baseDir, origText, sourceName, varBase):
401-
super().__init__(fileLoader, baseDir, origText, sourceName, varBase)
402-
self.prolog = []
403-
self.count = 0
404-
405401
def _includeFile(self, name):
406402
return '"$_BOB_TMP_BASE/' + escapePwsh(name) + '"'
407403

408404
def _includeLiteral(self, content):
409405
return quotePwsh(content.decode('utf8'))
410406

411-
def _resolveContent(self, result):
412-
return "\n".join(self.prolog + [result])
413-
414407

415408
class PwshLanguage:
416409
index = ScriptLanguage.PWSH
@@ -574,9 +567,150 @@ def setupFingerprint(spec, env):
574567
return [interpreter, "-c", spec.fingerprintScript]
575568

576569

570+
class PythonResolver(IncludeResolver):
571+
def _includeFile(self, name):
572+
return 'os.path.join(_BOB_TMP_BASE, ' + repr(name) + ')'
573+
574+
def _includeLiteral(self, content):
575+
return repr(content.decode('utf8'))
576+
577+
578+
class PythonLanguage:
579+
index = ScriptLanguage.PYTHON
580+
glue = "\nos.chdir(os.environ['BOB_CWD'])\n"
581+
Resolver = PythonResolver
582+
583+
HELPERS = dedent("""\
584+
from subprocess import run, call, check_call, check_output
585+
import os, os.path, sys
586+
""")
587+
588+
@staticmethod
589+
def __formatProlog(spec, tmpDir):
590+
pathSep = ";" if sys.platform == "win32" else ":"
591+
env = { key : repr(value) for (key, value) in spec.env.items() }
592+
env.update({
593+
"PATH": '"' + pathSep + '".join([' + ", ".join(
594+
[repr(os.path.abspath(p)) for p in spec.paths] +
595+
(['os.environ["PATH"]'] if not spec.hasSandbox else
596+
[repr(p) for p in spec.sandboxPaths])
597+
) + '])',
598+
"LD_LIBRARY_PATH": '"' + pathSep + '".join(' +
599+
repr([ os.path.abspath(p) for p in spec.libraryPaths ]) + ')',
600+
"BOB_CWD": repr(os.path.abspath(spec.workspaceExecPath)),
601+
})
602+
603+
ret = [
604+
"# Convenience helpers",
605+
PythonLanguage.HELPERS,
606+
"",
607+
"# Special Bob array variables:",
608+
"BOB_ALL_PATHS = dict({})".format(repr(sorted(
609+
[ (name, os.path.abspath(path)) for name, path in spec.allPaths ]
610+
))),
611+
"BOB_DEP_PATHS = dict({})".format(repr(sorted(
612+
[ (name, os.path.abspath(path)) for name,path in spec.depPaths ]
613+
))),
614+
"BOB_TOOL_PATHS = dict({})".format(repr(sorted(
615+
[ (name, os.path.abspath(path)) for name,path in spec.toolPaths ]
616+
))),
617+
'_BOB_TMP_BASE = ' + repr("/tmp" if spec.hasSandbox else os.path.join(tmpDir, "tmp")),
618+
"",
619+
"# Environment:",
620+
"\n".join('os.environ["{}"] = {}'.format(k, v) for (k,v) in sorted(env.items())),
621+
]
622+
return "\n".join(ret)
623+
624+
@staticmethod
625+
def __formatSetup(spec):
626+
return "\n".join([
627+
"",
628+
"# Recipe setup script",
629+
spec.setupScript,
630+
"os.chdir(os.environ['BOB_CWD'])",
631+
])
632+
633+
@staticmethod
634+
def __formatScript(spec, tmpDir, trace):
635+
if spec.envFile:
636+
envFile = "/bob/env" if spec.hasSandbox else os.path.abspath(spec.envFile)
637+
else:
638+
envFile = None
639+
ret = [
640+
PythonLanguage.__formatProlog(spec, tmpDir),
641+
"",
642+
"# Setup",
643+
dedent("""\
644+
with open({ENV_FILE}, "w") as f:
645+
f.write(repr({{ "env" : dict(os.environ), "globals" : globals() }}))
646+
""".format(ENV_FILE=repr(envFile))) if envFile else "",
647+
"os.chdir(os.environ['BOB_CWD'])",
648+
"",
649+
PythonLanguage.__formatSetup(spec),
650+
"",
651+
"# Recipe main script",
652+
spec.mainScript,
653+
]
654+
return "\n".join(ret)
655+
656+
@staticmethod
657+
def __scriptFilePaths(spec, tmpDir):
658+
if spec.hasSandbox:
659+
execScriptFile = "/.script.py"
660+
realScriptFile = (spec.scriptHint or os.path.join(tmpDir, ".script")) + ".py"
661+
else:
662+
execScriptFile = (spec.scriptHint or os.path.join(tmpDir, "script")) + ".py"
663+
realScriptFile = execScriptFile
664+
return (os.path.abspath(realScriptFile), os.path.abspath(execScriptFile))
665+
666+
@staticmethod
667+
def setupShell(spec, tmpDir, keepEnv):
668+
realScriptFile, execScriptFile = PythonLanguage.__scriptFilePaths(spec, tmpDir)
669+
with open(realScriptFile, "w") as f:
670+
f.write(PythonLanguage.__formatProlog(spec, tmpDir))
671+
f.write(PythonLanguage.__formatSetup(spec))
672+
673+
args = [sys.executable, "-i", execScriptFile]
674+
args.extend(os.path.abspath(a) for a in spec.args)
675+
676+
return (realScriptFile, execScriptFile, args)
677+
678+
@staticmethod
679+
def setupCall(spec, tmpDir, keepEnv, trace):
680+
realScriptFile, execScriptFile = PythonLanguage.__scriptFilePaths(spec, tmpDir)
681+
with open(realScriptFile, "w") as f:
682+
f.write(PythonLanguage.__formatScript(spec, tmpDir, trace))
683+
684+
args = [sys.executable, execScriptFile]
685+
args.extend(os.path.abspath(a) for a in spec.args)
686+
687+
return (realScriptFile, execScriptFile, args)
688+
689+
@staticmethod
690+
def mangleFingerprints(scriptFragments, env):
691+
# join the script fragments first
692+
script = joinScripts(scriptFragments, PythonLanguage.glue)
693+
694+
# do not add preamble for empty scripts
695+
if not script: return ""
696+
697+
# Add snippets as they match and a default settings preamble
698+
ret = [script]
699+
for n,v in sorted(env.items()):
700+
ret.append('os.environ["{}"] = {}'.format(k, repr(v)))
701+
ret.append(PythonLanguage.HELPERS)
702+
703+
return "\n".join(reversed(ret))
704+
705+
@staticmethod
706+
def setupFingerprint(spec, env):
707+
return [sys.executable, "-c", spec.fingerprintScript]
708+
709+
577710
LANG = {
578711
ScriptLanguage.BASH : BashLanguage,
579712
ScriptLanguage.PWSH : PwshLanguage,
713+
ScriptLanguage.PYTHON : PythonLanguage,
580714
}
581715

582716
def getLanguage(language):

0 commit comments

Comments
 (0)