Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Python language support #423

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 65 additions & 16 deletions pym/bob/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from . import BOB_VERSION, BOB_INPUT_HASH, DEBUG
from .errors import ParseError, BobError
from .languages import getLanguage, ScriptLanguage, BashLanguage, PwshLanguage
from .languages import getLanguage, ScriptLanguage, BashLanguage, PwshLanguage, PythonLanguage
from .pathspec import PackageSet
from .scm import CvsScm, GitScm, ImportScm, SvnScm, UrlScm, ScmOverride, \
auditFromDir, getScm, SYNTHETIC_SCM_PROPS
Expand Down Expand Up @@ -115,9 +115,11 @@ def fetchFingerprintScripts(recipe):
recipe.get("fingerprintScript")),
ScriptLanguage.PWSH : recipe.get("fingerprintScriptPwsh",
recipe.get("fingerprintScript")),
ScriptLanguage.PYTHON : recipe.get("fingerprintScriptPython",
recipe.get("fingerprintScript")),
}

def fetchScripts(recipe, prefix, resolveBash, resolvePwsh):
def fetchScripts(recipe, prefix, resolveBash, resolvePwsh, resolvePython):
return {
ScriptLanguage.BASH : (
resolveBash(recipe.get(prefix + "SetupBash", recipe.get(prefix + "Setup")),
Expand All @@ -130,21 +132,31 @@ def fetchScripts(recipe, prefix, resolveBash, resolvePwsh):
prefix + "Setup[Pwsh]"),
resolvePwsh(recipe.get(prefix + "ScriptPwsh", recipe.get(prefix + "Script")),
prefix + "Script[Pwsh]"),
)
),
ScriptLanguage.PYTHON : (
resolvePython(recipe.get(prefix + "SetupPython", recipe.get(prefix + "Setup")),
prefix + "Setup[Python]"),
resolvePython(recipe.get(prefix + "ScriptPython", recipe.get(prefix + "Script")),
prefix + "Script[Python]"),
),
}

def mergeScripts(fragments, glue):
"""Join all scripts of the recipe and its classes.

The result is a tuple with (setupScript, mainScript, digestScript)
The result is a tuple with (setupScript, mainScript, digestScript, includedFiles)
"""

return (
joinScripts((f[0][0] for f in fragments), glue),
joinScripts((f[1][0] for f in fragments), glue),
joinScripts(
( joinScripts((f[0][1] for f in fragments), "\n"),
joinScripts((f[1][1] for f in fragments), "\n"),
), "\n")
), "\n"),
{ name : content for name, content in
chain.from_iterable(chain(f[0][2].items(), f[1][2].items()) for f in fragments)
},
)


Expand Down Expand Up @@ -790,6 +802,9 @@ def getPostRunCmds(self):
def getDigestScript(self):
raise NotImplementedError

def getIncludedFiles(self):
raise NotImplementedError

def getLabel(self):
raise NotImplementedError

Expand Down Expand Up @@ -1042,6 +1057,9 @@ def getDigestScript(self):
"""
return self._coreStep.getDigestScript()

def getIncludedFiles(self):
return self._coreStep.getIncludedFiles()

def isDeterministic(self):
"""Return whether the step is deterministic.

Expand Down Expand Up @@ -1458,6 +1476,9 @@ def getDigestScript(self):
else:
return None

def getIncludedFiles(self):
return self.corePackage.recipe.checkoutIncludedFiles

@property
def fingerprintMask(self):
return 0
Expand Down Expand Up @@ -1537,7 +1558,7 @@ def hasNetAccess(self):
class CoreBuildStep(CoreStep):
__slots__ = []

def __init__(self, corePackage, script=(None, None, None), digestEnv=Env(), env=Env(), args=[]):
def __init__(self, corePackage, script=(None, None, None, {}), digestEnv=Env(), env=Env(), args=[]):
isValid = script[1] is not None
super().__init__(corePackage, isValid, True, digestEnv, env, args)

Expand Down Expand Up @@ -1568,6 +1589,9 @@ def getMainScript(self):
def getDigestScript(self):
return self.corePackage.recipe.buildDigestScript

def getIncludedFiles(self):
return self.corePackage.recipe.buildIncludedFiles

@property
def fingerprintMask(self):
# Remove bits of all tools that are not used in buildStep
Expand All @@ -1591,7 +1615,7 @@ def hasNetAccess(self):
class CorePackageStep(CoreStep):
__slots__ = []

def __init__(self, corePackage, script=(None, None, None), digestEnv=Env(), env=Env(), args=[]):
def __init__(self, corePackage, script=(None, None, None, {}), digestEnv=Env(), env=Env(), args=[]):
isValid = script[1] is not None
super().__init__(corePackage, isValid, True, digestEnv, env, args)

Expand Down Expand Up @@ -1622,6 +1646,9 @@ def getMainScript(self):
def getDigestScript(self):
return self.corePackage.recipe.packageDigestScript

def getIncludedFiles(self):
return self.corePackage.recipe.packageIncludedFiles

@property
def fingerprintMask(self):
return self.corePackage.fingerprintMask
Expand Down Expand Up @@ -1884,7 +1911,7 @@ def resolve(self, text, section):
raise ParseError("Bad substiturion in {}: {}".format(section, str(e)))
return resolver.resolve(ret)
else:
return (None, None)
return (None, None, {})

def mergeFilter(left, right):
if left is None:
Expand Down Expand Up @@ -2168,9 +2195,11 @@ def __init__(self, recipeSet, recipe, layer, sourceFile, baseDir, packageName, b
baseDir, packageName, sourceName).resolve
incHelperPwsh = IncludeHelper(PwshLanguage, recipeSet.loadBinary,
baseDir, packageName, sourceName).resolve
incHelperPython = IncludeHelper(PythonLanguage, recipeSet.loadBinary,
baseDir, packageName, sourceName).resolve

self.__scriptLanguage = recipe.get("scriptLanguage")
self.__checkout = fetchScripts(recipe, "checkout", incHelperBash, incHelperPwsh)
self.__checkout = fetchScripts(recipe, "checkout", incHelperBash, incHelperPwsh, incHelperPython)
self.__checkoutSCMs = recipe.get("checkoutSCM", [])
for scm in self.__checkoutSCMs:
scm["__source"] = sourceName
Expand All @@ -2180,8 +2209,8 @@ def __init__(self, recipeSet, recipe, layer, sourceFile, baseDir, packageName, b
for a in self.__checkoutAsserts:
a["__source"] = sourceName + ", checkoutAssert #{}".format(i)
i += 1
self.__build = fetchScripts(recipe, "build", incHelperBash, incHelperPwsh)
self.__package = fetchScripts(recipe, "package", incHelperBash, incHelperPwsh)
self.__build = fetchScripts(recipe, "build", incHelperBash, incHelperPwsh, incHelperPython)
self.__package = fetchScripts(recipe, "package", incHelperBash, incHelperPwsh, incHelperPython)
self.__fingerprintScriptList = fetchFingerprintScripts(recipe)
self.__fingerprintIf = recipe.get("fingerprintIf")
self.__fingerprintVarsList = set(recipe.get("fingerprintVars", []))
Expand Down Expand Up @@ -2316,7 +2345,7 @@ def coDet(r):

# the package step must always be valid
if self.__package[1] is None:
self.__package = (None, "", 'da39a3ee5e6b4b0d3255bfef95601890afd80709')
self.__package = (None, "", 'da39a3ee5e6b4b0d3255bfef95601890afd80709', {})

# final shared value
self.__shared = self.__shared == True
Expand Down Expand Up @@ -2643,7 +2672,7 @@ def prepare(self, inputEnv, sandboxEnabled, inputStates, inputSandbox=None,
directPackages, indirectPackages, states, uidGen(), doFingerprint)

# optional checkout step
if self.__checkout != (None, None, None) or self.__checkoutSCMs or self.__checkoutAsserts:
if self.__checkout != (None, None, None, {}) or self.__checkoutSCMs or self.__checkoutAsserts:
checkoutDigestEnv = env.prune(self.__checkoutVars)
checkoutEnv = ( env.prune(self.__checkoutVars | self.__checkoutVarsWeak)
if self.__checkoutVarsWeak else checkoutDigestEnv )
Expand All @@ -2653,7 +2682,7 @@ def prepare(self, inputEnv, sandboxEnabled, inputStates, inputSandbox=None,
srcCoreStep = p.createInvalidCoreCheckoutStep()

# optional build step
if self.__build != (None, None, None):
if self.__build != (None, None, None, {}):
buildDigestEnv = env.prune(self.__buildVars)
buildEnv = ( env.prune(self.__buildVars | self.__buildVarsWeak)
if self.__buildVarsWeak else buildDigestEnv )
Expand Down Expand Up @@ -2765,6 +2794,10 @@ def checkoutMainScript(self):
def checkoutDigestScript(self):
return self.__checkout[2] or ""

@property
def checkoutIncludedFiles(self):
return self.__checkout[3]

@property
def checkoutDeterministic(self):
return self.__checkoutDeterministic
Expand Down Expand Up @@ -2793,6 +2826,10 @@ def buildMainScript(self):
def buildDigestScript(self):
return self.__build[2]

@property
def buildIncludedFiles(self):
return self.__build[3]

@property
def buildVars(self):
return self.__buildVars
Expand All @@ -2813,6 +2850,10 @@ def packageMainScript(self):
def packageDigestScript(self):
return self.__package[2]

@property
def packageIncludedFiles(self):
return self.__package[3]

@property
def packageVars(self):
return self.__packageVars
Expand Down Expand Up @@ -3024,7 +3065,7 @@ class RecipeSet:
),
schema.Optional('layers') : [str],
schema.Optional('scriptLanguage',
default=ScriptLanguage.BASH) : schema.And(schema.Or("bash", "PowerShell"),
default=ScriptLanguage.BASH) : schema.And(schema.Or("bash", "PowerShell", "python"),
schema.Use(ScriptLanguage)),
})

Expand Down Expand Up @@ -3622,21 +3663,27 @@ def __createSchemas(self):
schema.Optional('checkoutScript') : str,
schema.Optional('checkoutScriptBash') : str,
schema.Optional('checkoutScriptPwsh') : str,
schema.Optional('checkoutScriptPython') : str,
schema.Optional('checkoutSetup') : str,
schema.Optional('checkoutSetupBash') : str,
schema.Optional('checkoutSetupPwsh') : str,
schema.Optional('checkoutSetupPython') : str,
schema.Optional('buildScript') : str,
schema.Optional('buildScriptBash') : str,
schema.Optional('buildScriptPwsh') : str,
schema.Optional('buildScriptPython') : str,
schema.Optional('buildSetup') : str,
schema.Optional('buildSetupBash') : str,
schema.Optional('buildSetupPwsh') : str,
schema.Optional('buildSetupPython') : str,
schema.Optional('packageScript') : str,
schema.Optional('packageScriptBash') : str,
schema.Optional('packageScriptPwsh') : str,
schema.Optional('packageScriptPython') : str,
schema.Optional('packageSetup') : str,
schema.Optional('packageSetupBash') : str,
schema.Optional('packageSetupPwsh') : str,
schema.Optional('packageSetupPython') : str,
schema.Optional('checkoutTools') : [ toolNameSchema ],
schema.Optional('buildTools') : [ toolNameSchema ],
schema.Optional('packageTools') : [ toolNameSchema ],
Expand Down Expand Up @@ -3674,6 +3721,7 @@ def __createSchemas(self):
schema.Optional('fingerprintScript', default="") : str,
schema.Optional('fingerprintScriptBash') : str,
schema.Optional('fingerprintScriptPwsh', default="") : str,
schema.Optional('fingerprintScriptPython', default="") : str,
schema.Optional('fingerprintIf') : schema.Or(None, str, bool, IfExpression),
schema.Optional('fingerprintVars') : [ varNameUseSchema ],
})
Expand All @@ -3694,9 +3742,10 @@ def __createSchemas(self):
schema.Optional('fingerprintScript', default="") : str,
schema.Optional('fingerprintScriptBash') : str,
schema.Optional('fingerprintScriptPwsh', default="") : str,
schema.Optional('fingerprintScriptPython', default="") : str,
schema.Optional('fingerprintIf') : schema.Or(None, str, bool, IfExpression),
schema.Optional('fingerprintVars') : [ varNameUseSchema ],
schema.Optional('scriptLanguage') : schema.And(schema.Or("bash", "PowerShell"),
schema.Optional('scriptLanguage') : schema.And(schema.Or("bash", "PowerShell", "python"),
schema.Use(ScriptLanguage)),
schema.Optional('jobServer') : bool,
}
Expand Down
13 changes: 12 additions & 1 deletion pym/bob/invoker.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import os
import re
import shutil
import stat
import subprocess
import sys
import tempfile
Expand Down Expand Up @@ -266,7 +267,6 @@ def __getSandboxCmds(self, tmpDir):
#FIXME: if verbosity >= 4: cmdArgs.append('-D')
cmdArgs.extend(["-S", tmpDir])
cmdArgs.extend(["-H", "bob"])
cmdArgs.extend(["-d", "/tmp"])
sandboxRootFs = os.path.abspath(self.__spec.sandboxRootWorkspace)
for f in os.listdir(sandboxRootFs):
cmdArgs.extend(["-M", os.path.join(sandboxRootFs, f), "-m", "/"+f])
Expand Down Expand Up @@ -301,6 +301,16 @@ async def executeStep(self, mode, clean=False, keepSandbox=False):
# also used as ephemeral sandbox container.
tmpDir = tempfile.mkdtemp()

# Create /tmp in sandbox container. Receives included files and
# needs to be there anyway.
os.mkdir(os.path.join(tmpDir, "tmp"), 0o755)
for name, content in self.__spec.includedFiles.items():
fn = os.path.join(tmpDir, "tmp", name)
with open(fn, "wb") as f:
f.write(content)
# Set explicit mode to retain Bob 0.19 behaviour.
os.chmod(fn, stat.S_IREAD|stat.S_IWRITE)

# prepare workspace
clean = self.__spec.clean if self.__spec.clean is not None else clean
if not os.path.isdir(self.__spec.workspaceWorkspacePath):
Expand Down Expand Up @@ -468,6 +478,7 @@ async def executeFingerprint(self, keepSandbox=False):

# Setup workspace
cmdArgs = self.__getSandboxCmds(tmpDir)
cmdArgs.extend(["-d", "/tmp"])
cmdArgs.extend(["-d", "/bob/fingerprint"])
cmdArgs.extend(["-W", "/bob/fingerprint"])

Expand Down
Loading