GP-4367: Package dbgmodel (ghidradbg) better

This commit is contained in:
Dan 2024-02-28 14:15:33 -05:00
parent 9934159e25
commit 5b16857468
21 changed files with 269 additions and 219 deletions

View File

@ -21,7 +21,6 @@ apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
apply from: "$rootProject.projectDir/gradle/debugger/hasNodepJar.gradle"
apply from: "$rootProject.projectDir/gradle/debugger/hasPythonPackage.gradle"
apply from: "buildNatives.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Debug Debugger-agent-dbgeng'
@ -37,12 +36,45 @@ dependencies {
testImplementation project(path: ":Debugger-gadp", configuration: 'testArtifacts')
}
// Include buildable native source in distribution
rootProject.assembleDistribution {
from (this.project.projectDir.toString()) {
include "src/**"
into { getZipPath(this.project) }
}
if ("win_x86_64".equals(getCurrentPlatformName())) {
String makeName = "win_x86_64DbgmodelTlbMake"
task(type: Exec, makeName) {
ext.tmpBatch = file("build/buildTlb.bat")
ext.idl = file("src/main/py/src/ghidradbg/dbgmodel/DbgModel.idl")
ext.tlb = file("build/os/win_x86_64/dbgmodel.tlb")
inputs.file(idl)
outputs.file(tlb)
doFirst {
file(tlb).parentFile.mkdirs()
def midlCmd = "midl /tlb ${tlb} ${idl}"
println "Executing: " + midlCmd
tmpBatch.withWriter { out ->
out.println "call " + VISUAL_STUDIO_VCVARS_CMD
out.println midlCmd
}
}
doLast {
assert file(tlb).exists() : "Failed to build dbgmodel.tlb"
}
commandLine "cmd", "/c", tmpBatch
}
tasks.assemblePyPackage {
from(tasks."$makeName") {
into("src/ghidradbg/dbgmodel/tlb")
}
}
}
else {
tasks.assemblePyPackage {
from(rootProject.BIN_REPO + '/' + getGhidraRelativePath(project) + "/os/win_x86_64/dbgmodel.tlb") {
into("src/ghidradbg/dbgmodel/tlb")
}
}
}
tasks.nodepJar {

View File

@ -1,49 +0,0 @@
/* ###
* IP: GHIDRA
*
* 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.
*/
if ("win_x86_64".equals(getCurrentPlatformName())) {
String makeName = "win_x86_64TlbMake"
task(type: Exec, makeName) {
def projectPath = projectDir.toString()
def solutionBatchFilePath = projectPath + "/build/buildSolution.bat"
def projectPathWindows = projectPath.replace("/", File.separator)
doFirst {
file("build/os/win_x86_64").mkdirs()
def srcdir = "src/main/py/src/dbgmodel"
def msbuildCmd = "midl /tlb build/os/win_x86_64/dbgmodel.tlb ${srcdir}/DbgModel.idl"
println "Executing: " + msbuildCmd
new File(solutionBatchFilePath).withWriter { out ->
out.println "call " + VISUAL_STUDIO_VCVARS_CMD
out.println msbuildCmd
}
}
doLast {
assert file("build/os/win_x86_64/dbgmodel.tlb").exists() : "Failed to build dbgmodel.tlb"
}
executable "cmd"
args "/c"
args solutionBatchFilePath.replace("/", File.separator)
}
}

View File

@ -5,5 +5,5 @@ data/debugger-launchers/local-dbgeng.bat||GHIDRA||||END|
src/main/py/LICENSE||GHIDRA||||END|
src/main/py/README.md||GHIDRA||||END|
src/main/py/pyproject.toml||GHIDRA||||END|
src/main/py/src/dbgmodel/DbgModel.idl||GHIDRA||||END|
src/main/py/src/ghidradbg/dbgmodel/DbgModel.idl||GHIDRA||||END|
src/main/py/src/ghidradbg/schema.xml||GHIDRA||||END|

View File

@ -18,7 +18,7 @@ classifiers = [
]
dependencies = [
"ghidratrace==10.4",
"pybag>=2.2.8"
"pybag>=2.2.9"
]
[project.urls]

View File

@ -13,45 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
from . import util, commands, methods, hooks
from dbgmodel.ihostdatamodelaccess import HostDataModelAccess
import ctypes
import platform
import os
# NOTE: libraries must precede EVERYTHING, esp pybag and DbgMod
ctypes.windll.kernel32.SetErrorMode(0x0001 | 0x0002 | 0x8000)
if platform.architecture()[0] == '64bit':
dbgdirs = [os.getenv('OPT_DBGMODEL_PATH'),
r'C:\Program Files\Windows Kits\10\Debuggers\x64',
r'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64']
else:
dbgdirs = [os.getenv('OPT_DBGMODEL_PATH'),
r'C:\Program Files\Windows Kits\10\Debuggers\x86',
r'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86']
dbgdir = None
for _dir in dbgdirs:
if os.path.exists(_dir):
dbgdir = _dir
break
if not dbgdir:
raise RuntimeError("Windbg install directory not found!")
# preload these to get correct DLLs loaded
try:
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'dbghelp.dll'))
except Exception as exc:
print(f"LoadLibrary failed: {dbgdir}\dbghelp.dll {exc}")
pass
try:
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'dbgeng.dll'))
except Exception as exc:
print(f"LoadLibrary failed: {dbgdir}\dbgeng.dll {exc}")
pass
try:
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'DbgModel.dll'))
except Exception as exc:
print(f"LoadLibrary failed: {dbgdir}\dbgmodel.dll {exc}")
pass
from . import libraries, util, commands, methods, hooks

View File

@ -14,11 +14,11 @@
# limitations under the License.
##
from ghidratrace.client import Address, RegVal
from pybag import pydbg
from . import util
language_map = {
'ARM': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A', 'ARM:BE:64:v8', 'ARM:LE:64:v8'],
'Itanium': [],

View File

@ -22,15 +22,14 @@ import socket
import sys
import time
from ghidratrace import sch
from ghidratrace.client import Client, Address, AddressRange, TraceObject
from pybag import pydbg, userdbg, kerneldbg
from pybag.dbgeng import core as DbgEng
from pybag.dbgeng import exception
from dbgmodel.imodelobject import ModelObjectKind
from ghidratrace import sch
from ghidratrace.client import Client, Address, AddressRange, TraceObject
from . import util, arch, methods, hooks
from .dbgmodel.imodelobject import ModelObjectKind
PAGE_SIZE = 4096

View File

@ -0,0 +1,20 @@
## ###
# IP: GHIDRA
#
# 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 os
def module_locator():
return os.path.dirname(os.path.realpath(__file__))

View File

@ -13,12 +13,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *
from comtypes.hresult import S_OK, S_FALSE
from ctypes import *
from comtypes.gen import DbgMod
from comtypes.hresult import S_OK, S_FALSE
from pybag.dbgeng import exception
import dbgmodel.imodelobject as mo
from . import imodelobject as mo
class DataModelManager(object):
def __init__(self, mgr):
@ -41,43 +43,43 @@ class DataModelManager(object):
def AcquireNamedModel(self, modelName, modelObject):
raise exception.E_NOTIMPL_Error
def Close(self):
raise exception.E_NOTIMPL_Error
def CreateNoValue(self, object):
raise exception.E_NOTIMPL_Error
def CreateErrorObject(self, error, message, object):
raise exception.E_NOTIMPL_Error
def CreateTypedObject(self, context, objectLocation, objectType, object):
raise exception.E_NOTIMPL_Error
def CreateTypedObjectByReference(self, context, objectLocation, objectType, object):
raise exception.E_NOTIMPL_Error
def CreateSyntheticObject(self, context, object):
raise exception.E_NOTIMPL_Error
def CreateDataModelObject(self, dataModel, object):
raise exception.E_NOTIMPL_Error
def CreateTypedIntrinsicObject(self, intrinsicData, type, object):
raise exception.E_NOTIMPL_Error
def CreateIntrinsicObject(self, objectKind, intrinsicData, object):
raise exception.E_NOTIMPL_Error
def GetModelForTypeSignature(self, typeSignature, dataModel):
raise exception.E_NOTIMPL_Error
def GetModelForType(self, type, dataModel, typeSignature, wildcardMatches):
raise exception.E_NOTIMPL_Error
def RegisterExtensionForTypeSignature(self, typeSignature, dataModel):
raise exception.E_NOTIMPL_Error
def RegisterModelForTypeSignature(self, typeSignature, dataModel):
raise exception.E_NOTIMPL_Error
@ -92,5 +94,3 @@ class DataModelManager(object):
def UnregisterNamedModel(self, modelName):
raise exception.E_NOTIMPL_Error

View File

@ -13,13 +13,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *
from comtypes.hresult import S_OK, S_FALSE
from ctypes import *
from comtypes.gen import DbgMod
from comtypes.hresult import S_OK, S_FALSE
from pybag.dbgeng import exception
from pybag.dbgeng import win32
class DebugHost(object):
def __init__(self, host):
self._host = host
@ -38,11 +39,6 @@ class DebugHost(object):
def GetDefaultMetadata(self, metadata):
raise exception.E_NOTIMPL_Error
def GetHostDefinedInterface(self, hostUnk):
raise exception.E_NOTIMPL_Error

View File

@ -13,14 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *
from comtypes.hresult import S_OK, S_FALSE
from ctypes import *
from comtypes.gen import DbgMod
from comtypes.hresult import S_OK, S_FALSE
from pybag.dbgeng import exception
from .idatamodelmanager import DataModelManager
from .idebughost import DebugHost
from .idebughost import DebugHost
class HostDataModelAccess(object):
def __init__(self, hdma):
@ -41,6 +42,3 @@ class HostDataModelAccess(object):
hr = self._hdma.GetDataModel(byref(manager), byref(host))
exception.check_err(hr)
return (DataModelManager(manager), DebugHost(host))

View File

@ -13,20 +13,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *
from comtypes import COMError
from comtypes.hresult import S_OK, S_FALSE
from ctypes import *
from comtypes import COMError
from comtypes.gen import DbgMod
from comtypes.hresult import S_OK, S_FALSE
from pybag.dbgeng import exception
from .imodeliterator import ModelIterator
class IterableConcept(object):
def __init__(self, concept):
self._concept = concept
concept.AddRef()
# IterableConcept
def GetDefaultIndexDimensionality(self, context, dimensionality):
@ -39,6 +40,3 @@ class IterableConcept(object):
except COMError as ce:
return None
return ModelIterator(iterator)

View File

@ -13,13 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *
from comtypes import BSTR
from comtypes.hresult import S_OK, S_FALSE
from ctypes import *
from comtypes import BSTR
from comtypes.gen import DbgMod
from comtypes.hresult import S_OK, S_FALSE
from pybag.dbgeng import exception
import dbgmodel.imodelobject as mo
from . import imodelobject as mo
class KeyEnumerator(object):
def __init__(self, keys):
@ -46,6 +48,3 @@ class KeyEnumerator(object):
def Reset(self):
hr = self._keys.Reset()
exception.check_err(hr)

View File

@ -13,13 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *
from comtypes import COMError
from comtypes.hresult import S_OK, S_FALSE
from ctypes import *
from comtypes import COMError
from comtypes.gen import DbgMod
from comtypes.hresult import S_OK, S_FALSE
from pybag.dbgeng import exception
import dbgmodel.imodelobject as mo
from . import imodelobject as mo
class ModelIterator(object):
def __init__(self, iter):
@ -33,7 +35,8 @@ class ModelIterator(object):
indexer = POINTER(DbgMod.IModelObject)()
metadata = POINTER(DbgMod.IKeyStore)()
try:
self._iter.GetNext(byref(object), dimensions, byref(indexer), byref(metadata))
self._iter.GetNext(byref(object), dimensions,
byref(indexer), byref(metadata))
except COMError as ce:
return None
index = mo.ModelObject(indexer)
@ -43,6 +46,3 @@ class ModelIterator(object):
def Reset(self):
hr = self._keys.Reset()
exception.check_err(hr)

View File

@ -13,18 +13,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *
from comtypes import IUnknown, COMError
from comtypes.automation import IID, VARIANT
from comtypes.hresult import S_OK, S_FALSE
from comtypes.gen.DbgMod import *
from enum import Enum
from ctypes import *
from enum import Enum
from comtypes import IUnknown, COMError
from comtypes.automation import IID, VARIANT
from comtypes.gen import DbgMod
from comtypes.hresult import S_OK, S_FALSE
from pybag.dbgeng import exception
from .ikeyenumerator import KeyEnumerator
from comtypes.gen.DbgMod import *
from .iiterableconcept import IterableConcept
from .ikeyenumerator import KeyEnumerator
class ModelObjectKind(Enum):
PROPERTY_ACCESSOR = 0
@ -38,6 +40,7 @@ class ModelObjectKind(Enum):
METHOD = 8
KEY_REFERENCE = 9
class ModelObject(object):
def __init__(self, obj):
self._obj = obj
@ -56,42 +59,42 @@ class ModelObject(object):
def AddParentModel(self, model, contextObject, override):
raise exception.E_NOTIMPL_Error
def ClearConcepts(self):
raise exception.E_NOTIMPL_Error
def ClearKeys(self):
raise exception.E_NOTIMPL_Error
def Compare(self, other, equal):
raise exception.E_NOTIMPL_Error
def Dereference(self, object):
raise exception.E_NOTIMPL_Error
def EnumerateKeyReferences(self):
raise exception.E_NOTIMPL_Error
def EnumerateKeys(self):
keys = POINTER(DbgMod.IKeyEnumerator)()
hr = self._obj.EnumerateKeys(byref(keys))
if hr != S_OK:
return None
return KeyEnumerator(keys)
def EnumerateKeyValues(self):
raise exception.E_NOTIMPL_Error
def EnumerateRawReferences(self, kind, searchFlags):
raise exception.E_NOTIMPL_Error
def EnumerateRawValues(self, kind, searchFlag):
keys = POINTER(DbgMod.IRawEnumerator)()
hr = self._obj.EnumerateRawValues(kind, searchFlag, byref(keys))
if hr != S_OK:
return None
return RawEnumerator(keys, kind)
def GetConcept(self, ref):
ifc = POINTER(IUnknown)()
metadata = POINTER(DbgMod.IKeyStore)()
@ -102,10 +105,10 @@ class ModelObject(object):
def GetContext(self, context):
raise exception.E_NOTIMPL_Error
def GetContextForDataModel(self, dataModelObject, context):
raise exception.E_NOTIMPL_Error
def GetIntrinsicValue(self):
var = VARIANT()
hr = self._obj.GetIntrinsicValue(var)
@ -123,7 +126,7 @@ class ModelObject(object):
raise exception.E_NOTIMPL_Error
def GetKeyValue(self, key):
kbuf = cast(c_wchar_p(key),POINTER(c_ushort))
kbuf = cast(c_wchar_p(key), POINTER(c_ushort))
value = POINTER(DbgMod.IModelObject)()
store = POINTER(DbgMod.IKeyStore)()
hr = self._obj.GetKeyValue(kbuf, byref(value), byref(store))
@ -142,16 +145,16 @@ class ModelObject(object):
def GetNumberOfParentModels(self, numModels):
raise exception.E_NOTIMPL_Error
def GetParentModel(self, i, model, context):
raise exception.E_NOTIMPL_Error
def GetRawReference(self, kind, name, searchFlags, object):
raise exception.E_NOTIMPL_Error
def GetRawValue(self, kind, name, searchFlags, object):
raise exception.E_NOTIMPL_Error
def GetTargetInfo(self):
location = POINTER(DbgMod._Location)()
type = POINTER(DbgMod.IDebugHostType)()
@ -161,47 +164,47 @@ class ModelObject(object):
def GetTypeInfo(self, type):
raise exception.E_NOTIMPL_Error
def IsEqualTo(self, other, equal):
raise exception.E_NOTIMPL_Error
def RemoveParentModel(self, model):
raise exception.E_NOTIMPL_Error
def SetConcept(self, ref, interface, metadata):
raise exception.E_NOTIMPL_Error
def SetContextForDataModel(self, modelObject, context):
raise exception.E_NOTIMPL_Error
def SetKey(self, key, object, metadata):
raise exception.E_NOTIMPL_Error
def SetKeyValue(self, key, object):
raise exception.E_NOTIMPL_Error
def TryCastToRuntimeType(self, runtimeTypedObject):
raise exception.E_NOTIMPL_Error
# Auxiliary
def GetKeyValueMap(self):
map = {}
keys = self.EnumerateKeys()
(k,v) = keys.GetNext()
(k, v) = keys.GetNext()
while k is not None:
map[k.value] = self.GetKeyValue(k.value)
(k,v) = keys.GetNext()
(k, v) = keys.GetNext()
return map
def GetRawValueMap(self):
map = {}
kind = self.GetKind()
keys = self.EnumerateRawValues(kind, c_long(0))
(k,v) = keys.GetNext()
(k, v) = keys.GetNext()
while k is not None:
map[k.value] = v
(k,v) = keys.GetNext()
(k, v) = keys.GetNext()
return map
def GetAttributes(self):
@ -210,12 +213,11 @@ class ModelObject(object):
if kind == ModelObjectKind.ERROR:
return map
if kind == ModelObjectKind.INTRINSIC or \
kind == ModelObjectKind.TARGET_OBJECT or \
kind == ModelObjectKind.TARGET_OBJECT_REFERENCE:
kind == ModelObjectKind.TARGET_OBJECT or \
kind == ModelObjectKind.TARGET_OBJECT_REFERENCE:
return self.GetRawValueMap()
return self.GetKeyValueMap()
def GetElements(self):
list = []
if self.concept is None:
@ -248,7 +250,7 @@ class ModelObject(object):
if "x" not in idx:
idx = int(idx)
else:
idx = int(idx,16)
idx = int(idx, 16)
next = next.GetElement(idx)
else:
next = next.GetKeyValue(element)
@ -268,6 +270,5 @@ class ModelObject(object):
kind = self.GetKind()
if kind == ModelObjectKind.TARGET_OBJECT or \
kind == ModelObjectKind.INTRINSIC:
return self.GetTargetInfo()
return self.GetTargetInfo()
return None

View File

@ -13,13 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
from ctypes import *
from comtypes import BSTR
from comtypes.hresult import S_OK, S_FALSE
from ctypes import *
from comtypes import BSTR
from comtypes.gen import DbgMod
from comtypes.hresult import S_OK, S_FALSE
from pybag.dbgeng import exception
import dbgmodel.imodelobject as mo
from . import imodelobject as mo
class RawEnumerator(object):
def __init__(self, keys, kind):
@ -46,6 +48,3 @@ class RawEnumerator(object):
def Reset(self):
hr = self._keys.Reset()
exception.check_err(hr)

View File

@ -0,0 +1,69 @@
## ###
# IP: GHIDRA
#
# 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 ctypes
import os
import platform
import comtypes
import comtypes.client
from ghidradbg import dbgmodel
ctypes.windll.kernel32.SetErrorMode(0x0001 | 0x0002 | 0x8000)
if platform.architecture()[0] == '64bit':
dbgdirs = [os.getenv('OPT_DBGMODEL_PATH'),
r'C:\Program Files\Windows Kits\10\Debuggers\x64',
r'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64']
else:
dbgdirs = [os.getenv('OPT_DBGMODEL_PATH'),
r'C:\Program Files\Windows Kits\10\Debuggers\x86',
r'C:\Program Files (x86)\Windows Kits\10\Debuggers\x86']
dbgdir = None
for _dir in dbgdirs:
if _dir is not None and os.path.exists(_dir):
dbgdir = _dir
break
if not dbgdir:
raise RuntimeError("Windbg install directory not found!")
print(f"Loading dbgeng and friends from {dbgdir}")
# preload these to get correct DLLs loaded
try:
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'dbghelp.dll'))
except Exception as exc:
print(f"LoadLibrary failed: {dbgdir}\dbghelp.dll {exc}")
pass
try:
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'dbgeng.dll'))
except Exception as exc:
print(f"LoadLibrary failed: {dbgdir}\dbgeng.dll {exc}")
pass
try:
ctypes.windll.LoadLibrary(os.path.join(dbgdir, 'DbgModel.dll'))
except Exception as exc:
print(f"LoadLibrary failed: {dbgdir}\dbgmodel.dll {exc}")
pass
try:
from comtypes.gen import DbgMod
except:
tlb = os.path.join(dbgmodel.module_locator(), 'tlb', 'dbgmodel.tlb')
print(f"Loading TLB: {tlb}")
comtypes.client.GetModule(tlb)
from comtypes.gen import DbgMod

View File

@ -19,11 +19,10 @@ from io import StringIO
import re
import sys
from pybag import pydbg
from pybag.dbgeng import core as DbgEng, exception
from ghidratrace import sch
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
from pybag import pydbg
from pybag.dbgeng import core as DbgEng, exception
from . import util, commands

View File

@ -28,22 +28,16 @@ import traceback
from comtypes import CoClass, GUID
import comtypes
from comtypes.gen import DbgMod
from comtypes.hresult import S_OK
from pybag import pydbg, userdbg, kerneldbg, crashdbg
from pybag.dbgeng import core as DbgEng
from pybag.dbgeng import exception
from pybag.dbgeng import util as DbgUtil
from pybag.dbgeng.callbacks import DbgEngCallbacks
from dbgmodel.ihostdatamodelaccess import HostDataModelAccess
import comtypes.client
try:
from comtypes.gen import DbgMod
except:
tlb = "..\..\..\..\build\os\win_x86_64\dbgmodel.tlb"
comtypes.client.GetModule(tlb)
from comtypes.gen import DbgMod
from ghidradbg.dbgmodel.ihostdatamodelaccess import HostDataModelAccess
DbgVersion = namedtuple('DbgVersion', ['full', 'name', 'dotted', 'arch'])

View File

@ -14,6 +14,39 @@
* limitations under the License.
*/
def checkPythonVersion(String pyCmd) {
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine pyCmd, "-c", "import sys; print('{0}.{1}'.format(*sys.version_info))"
standardOutput = stdout
}
def version = "$stdout".strip()
println "$pyCmd is version $version"
return version
}
catch (Exception e) {
println "Could not run $pyCmd: $e"
return "ABSENT"
}
}
ext.SUPPORTED_PY_VERSIONS = ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
def findPython3() {
for (pyCmd in ['python3', 'python']) {
if (checkPythonVersion(pyCmd) in SUPPORTED_PY_VERSIONS) {
println "Using $pyCmd"
return pyCmd
}
}
println "Could not find compatible Python 3"
// Don't fail until task execution. Just let "python3" fail.
return 'python3'
}
ext.PYTHON3 = findPython3()
task assemblePyPackage(type: Copy) {
from "src/main/py"
into "build/pypkg/"
@ -27,7 +60,7 @@ task buildPyPackage(type: Exec) {
outputs.dir(dist)
workingDir { "build/pypkg" }
commandLine "python3", "-m", "build"
commandLine PYTHON3, "-m", "build"
}
// At the moment, any module with a python package also distributes it.