Skip to content

Commit

Permalink
Use configuration avoidance in runtime/build.gradle
Browse files Browse the repository at this point in the history
  • Loading branch information
mhsmith committed Aug 13, 2023
1 parent 6699367 commit c9030d2
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,25 +81,41 @@ public static String assetZip(String type, String abi) {
public static final String ASSET_BUILD_JSON = "build.json";
public static final String ASSET_CACERT = "cacert.pem";

// The default PATH on Mac is /usr/bin:/bin:/usr/sbin:/sbin. However, apps can't
// install anything into these locations, so the python.org installers use
// /usr/local/bin instead. This directory may also appear to be on the default PATH,
// but this is because it's listed in /etc/paths, which only affects shells, not
// other apps like Android Studio, or its Gradle subprocesses.
public static String findOnPath(String basename) throws FileNotFoundException {
public static String findExecutable(String name) throws FileNotFoundException {
File file = new File(name);
if (file.isAbsolute()) {
if (! file.exists()) {
throw new FileNotFoundException("'" + name + "' does not exist");
}
return name;
}

// The default PATH on Mac is /usr/bin:/bin:/usr/sbin:/sbin. However, apps can't
// install anything into these locations, so the python.org installers use
// /usr/local/bin instead. This directory may also appear to be on the default
// PATH, but this is because it's listed in /etc/paths, which only affects
// shells, but not other apps like Android Studio and its Gradle subprocesses.
List<String> path = new ArrayList<>();
Collections.addAll(path, System.getenv("PATH").split(File.pathSeparator));
if (System.getProperty("os.name").toLowerCase().startsWith("mac")) {
path.add("/usr/local/bin");
final String ETC_PATHS = "/etc/paths";
try (BufferedReader reader = new BufferedReader(new FileReader(ETC_PATHS))) {
String line;
while ((line = reader.readLine()) != null) {
path.add(line);
}
} catch (IOException e) {
System.out.println("Warning: while reading " + ETC_PATHS + ": " + e);
}
}
Collections.addAll(path, System.getenv("PATH").split(File.pathSeparator));

for (String dir : path) {
File file = new File(dir, basename);
file = new File(dir, name);
if (file.exists()) {
return file.toString();
}
}
throw new FileNotFoundException("Couldn't find '" + basename + "' on PATH");
throw new FileNotFoundException("Couldn't find '" + name + "' on PATH");
}

}
8 changes: 4 additions & 4 deletions product/gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import com.chaquo.python.internal.BuildCommon
import com.chaquo.python.internal.Common
import com.chaquo.python.internal.Common.findOnPath
import com.chaquo.python.internal.Common.findExecutable

plugins {
`java-gradle-plugin`
Expand Down Expand Up @@ -84,9 +84,9 @@ abstract class TestPythonTask : DefaultTask() {
pb.environment().putAll(environment)

command += if (System.getProperty("os.name").toLowerCase().contains("windows")) {
listOf(findOnPath("py"), "-$pythonVersion")
listOf(findExecutable("py"), "-$pythonVersion")
} else {
listOf(findOnPath("python$pythonVersion"))
listOf(findExecutable("python$pythonVersion"))
}
command += listOf("-m", "unittest")
val args = project.findProperty("testPythonArgs")
Expand Down Expand Up @@ -136,7 +136,7 @@ tasks.register("testIntegration") {
}

tasks.check {
dependsOn(tasks.named("testPython"), tasks.named("testIntegration"))
dependsOn("testPython", "testIntegration")
}

val INTEGRATION_DIR = "$projectDir/src/test/integration"
Expand Down
117 changes: 70 additions & 47 deletions product/runtime/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import com.chaquo.python.internal.BuildCommon;
import com.chaquo.python.internal.Common;
import static com.chaquo.python.internal.Common.findOnPath;
import static com.chaquo.python.internal.Common.findExecutable;

group = "com.chaquo.python.runtime"

Expand All @@ -23,7 +23,7 @@ def pyPlusVersionExe = "$pyPlusVersion"
if (osName == "windows") {
pyPlusVersionExe += ".exe"
}
pyPlusVersionExe = findOnPath(pyPlusVersionExe)
pyPlusVersionExe = findExecutable(pyPlusVersionExe)

String pythonHome
if (osName == "windows" && System.getenv("CI") != null) {
Expand All @@ -42,21 +42,27 @@ if (osName == "windows" && System.getenv("CI") != null) {
}
}


void addArtifact(Task task, Object filename, String pyVersion=null, String abi=null) {
File f = file(filename)
void addArtifact(TaskProvider task, File f, String pyVersion, String abi=null) {
def dotPos = f.name.lastIndexOf(".")
def dashPos = f.name.indexOf("-")
def name = f.name.substring(0, (dashPos != -1) ? dashPos : dotPos)
def pub = publishing.publications.maybeCreate(name, MavenPublication)
pub.artifactId = name
pub.artifact(f) {
builtBy task
def classifiers = [pyVersion, abi].findAll { it != null }
if (!classifiers.isEmpty()) {
setClassifier(classifiers.join("-"))

Provider<MavenPublication> pub
try {
pub = publishing.publications.named(name, MavenPublication)
} catch (UnknownDomainObjectException e) {
pub = publishing.publications.register(name, MavenPublication)
}
pub.configure {
artifactId = name
artifact(f) {
builtBy task
classifier = pyVersion
if (abi != null) {
classifier += "-$abi"
}
extension f.name.substring(dotPos + 1)
}
extension f.name.substring(dotPos + 1)
}
}

Expand All @@ -73,7 +79,8 @@ publishing {


Common.PYTHON_VERSIONS_SHORT.each { pyVersion ->
def compilePython = task("compilePython-$pyVersion", type: Copy, group: "build") {
def compilePython = tasks.register("compilePython-$pyVersion", Copy) {
group = "build"
doFirst {
delete(destinationDir)
mkdir(destinationDir)
Expand All @@ -88,9 +95,9 @@ Common.PYTHON_VERSIONS_SHORT.each { pyVersion ->
exec {
workingDir destinationDir
if (osName == "windows") {
commandLine findOnPath("py"), "-$pyVersion"
commandLine findExecutable("py"), "-$pyVersion"
} else {
commandLine findOnPath("python$pyVersion")
commandLine findExecutable("python$pyVersion")
}
args "-m", "compileall"
args "-q", "-b", "."
Expand All @@ -101,14 +108,19 @@ Common.PYTHON_VERSIONS_SHORT.each { pyVersion ->
}
}

def zipPython = task("zipPython-$pyVersion", type: Zip, group: "build") {
def zipFile = new File(
"$buildDir/${Common.ASSET_BOOTSTRAP}",
Common.assetZip("$Common.ASSET_BOOTSTRAP-$pyVersion")
)
def zipPython = tasks.register("zipPython-$pyVersion", Zip) {
group = "build"
from compilePython
destinationDirectory = new File(buildDir, Common.ASSET_BOOTSTRAP)
archiveFileName = Common.assetZip("$Common.ASSET_BOOTSTRAP-$pyVersion")
destinationDirectory = zipFile.parentFile
archiveFileName = zipFile.name
preserveFileTimestamps = false
reproducibleFileOrder = true
}
addArtifact(zipPython, zipPython.archivePath, pyVersion)
addArtifact(zipPython, zipFile, pyVersion)
}

String sdkPath(String path) {
Expand All @@ -125,7 +137,10 @@ String sdkPath(String path) {
// This does not affect the Java unit tests, which will use the same Java as Gradle itself.
def javaHome = BuildCommon.localProperty(project, 'chaquopy.java.home.8')

task("doc", group: "documentation")
def doc = tasks.register("doc") {
group = "documentation"
dependsOn("javadoc")
}

javadoc {
destinationDir = file("$docsDir/java")
Expand All @@ -138,14 +153,12 @@ javadoc {
addStringOption("link", "https://developer.android.com/reference/")
}
}
doc.dependsOn(javadoc)

def cythonTask = { String name, Closure closure ->
tasks.register(name, Exec) {
group = "build"
configure(closure)

def cythonTask = {Map taskArgs=[:], String name, Closure closure ->
taskArgs["type"] = Exec
taskArgs.putIfAbsent("group", "build")
def t = task(taskArgs, name, closure)
t.configure {
def pyxFiles = inputs.files.findAll { it.name.endsWith(".pyx") }
assert(pyxFiles.size() == 1)
def inFile = pyxFiles.get(0)
Expand All @@ -155,7 +168,7 @@ def cythonTask = {Map taskArgs=[:], String name, Closure closure ->
outputs.file outFile

workingDir inFile.parent // Reduce clutter in exception traces
executable findOnPath("cython")
executable findExecutable("cython")
args "-Wextra", "-Werror", inFile.name, "-I", outDir, "-o", outFile

doLast {
Expand Down Expand Up @@ -184,9 +197,7 @@ def cythonTask = {Map taskArgs=[:], String name, Closure closure ->
throw new GradleException("Failed to replace $cFile")
}
}

}
return t
}

cythonTask("cythonPython") {
Expand All @@ -212,7 +223,8 @@ dependencies {
testImplementation 'org.hamcrest:hamcrest-library:2.2'
}

task("generateStaticProxy", type: Exec, group: "verification") {
tasks.register("generateStaticProxy", Exec) {
group = "verification"
def outputDir = "$buildDir/static_proxy"
outputs.dir(outputDir)
outputs.upToDateWhen { false }
Expand All @@ -231,7 +243,8 @@ for (abi in ["host"] + Common.ABIS) {
def pyLibSuffix = ".so"
def cmakeBuildSubdir = "$buildDir/cmake/$abi"
def cmakeBuildType = findProperty("cmakeBuildType") ?: "Debug"
def cmake = task("cmake-$abi", type: Exec, group: "build") {
def cmake = tasks.register("cmake-$abi", Exec) {
group = "build"
dependsOn cythonPython, cythonJava
inputs.files "CMakeLists.txt"
// This is not a complete list of the outputs, but it's enough to detect when the
Expand Down Expand Up @@ -284,25 +297,28 @@ for (abi in ["host"] + Common.ABIS) {
projectDir
}

def cmakeBuild = task("cmakeBuild-$abi", type: Exec, group: "build") {
def cmakeBuild = tasks.register("cmakeBuild-$abi", Exec) {
// No inputs or outputs: the command itself determines whether it's up to date.
group = "build"
dependsOn cmake
executable "$sdkCmakeDir/bin/cmake"
args "--build", cmakeBuildSubdir
}
if (abi != "host") {
for (name in ["chaquopy", "libchaquopy_java"]) {
for (pyVersion in Common.PYTHON_VERSIONS_SHORT) {
addArtifact(cmakeBuild, "$cmakeBuildSubdir/$name-${pyVersion}.so",
pyVersion, abi)
addArtifact(
cmakeBuild, new File(cmakeBuildSubdir, "$name-${pyVersion}.so"),
pyVersion, abi)
}
}
}

if (abi == "host") {
def mainPythonDir = "$projectDir/src/main/python"
def testPythonDir = "$projectDir/src/test/python"
task("setupPythonPath", group: "verification") {
tasks.register("setupPythonPath") {
group = "verification"
dependsOn cmakeBuild
doFirst {
copy {
Expand All @@ -315,7 +331,7 @@ for (abi in ["host"] + Common.ABIS) {
}
}

def testCommonConfig = {
def hostConfig = {
dependsOn setupPythonPath, compileTestJava
environment "CLASSPATH", sourceSets.test.runtimeClasspath.asPath

Expand All @@ -331,7 +347,9 @@ for (abi in ["host"] + Common.ABIS) {

// For consistency with Android demo app, run via test suite rather than using
// "discover".
task("testPython", type: Exec, group: "verification") {
tasks.register("testPython", Exec) {
group = "verification"
configure(hostConfig)
workingDir "$projectDir/src/test/python"
environment "JAVA_HOME", javaHome
executable pyPlusVersionExe
Expand All @@ -342,8 +360,6 @@ for (abi in ["host"] + Common.ABIS) {
args "chaquopy.test"
}
}
testPython.configure(testCommonConfig)
check.dependsOn(testPython)

compileTestJava {
sourceCompatibility = "1.8"
Expand All @@ -353,7 +369,9 @@ for (abi in ["host"] + Common.ABIS) {
// Run via test suite for consistency with Android demo app. (NOTE: this will not show
// test results within IntelliJ for some reason.)
test.exclude "**" // Disable the default test task
task("testJava", type: Test, group: "verification") {
tasks.register("testJava", Test) {
group = "verification"
configure(hostConfig)
outputs.upToDateWhen { false }
environment "PYTHONHOME", pythonHome
if (osName == "linux") {
Expand All @@ -373,11 +391,12 @@ for (abi in ["host"] + Common.ABIS) {
showStandardStreams = true
}
}
testJava.configure(testCommonConfig)
check.dependsOn(testJava)
tasks.named("check") {
dependsOn("testPython", "testJava")
}

// Generates a script to start a Python REPL with Java and Python paths set up.
task("consoleScript") {
tasks.register("consoleScript") {
dependsOn setupPythonPath
doLast {
def writer = new PrintWriter("$projectDir/console.sh")
Expand All @@ -394,14 +413,18 @@ for (abi in ["host"] + Common.ABIS) {
}

// Requires the Python packages in requirements-docs.txt.
task("sphinx", type: Exec, group: "documentation") {
tasks.register("sphinx", Exec) {
group = "documentation"
configure(hostConfig)

// We call a specific Python executable rather than running `sphinx-build`,
// because Sphinx needs to be able to import the runtime module.
executable pyPlusVersionExe
args "-m", "sphinx", "-v", "-b", "html", "docs/sphinx", "build/docs"
environment "JAVA_HOME", javaHome
}
sphinx.configure(testCommonConfig)
doc.dependsOn(sphinx)
doc.configure {
dependsOn("sphinx")
}
}
}

0 comments on commit c9030d2

Please sign in to comment.