From: Thomas Walker Lynch Date: Fri, 27 Sep 2024 11:09:36 +0000 (+0000) Subject: includes DAG cycle finder, and rough drafts of the rest of the depdency graph build X-Git-Url: https://git.reasoningtechnology.com/usr/lib/python2.7/sre_compile.py?a=commitdiff_plain;h=26260703d1ef5c7dd72fa81c02cd6f0d276e7367;p=GQL-to-Cypher includes DAG cycle finder, and rough drafts of the rest of the depdency graph build --- diff --git a/build.gradle b/build.gradle index ec3a5bb..4312062 100644 --- a/build.gradle +++ b/build.gradle @@ -1,58 +1,94 @@ -defaultTasks 'installTools' +defaultTasks 'installTools', ':developer:preface' -/*-------------------------------------------------------------------------------- - - Configuration variables +//-------------------------------------------------------------------------------- +// Project manager should set these configuration variables +// +// tool versions +def antlrVersion = '4.11.1' +def jdkVersion = '22.0.1' +def jdkBuild = '8' -*/ +// The urls for upstream files +def antlrUrl = "https://www.antlr.org/download/antlr-${antlrVersion}-complete.jar" +def jdkUrl = "https://github.com/adoptium/temurin22-binaries/releases/download/jdk-${jdkVersion}%2B${jdkBuild}/OpenJDK22U-jdk_x64_linux_hotspot_${jdkVersion}_${jdkBuild}.tar.gz" +//-------------------------------------------------------------------------------- // project structure +// def repoHome = rootDir def toolDir = "${repoHome}/tool" def upstreamDir = "${toolDir}/upstream" def executorDir = "${toolDir}/executor" def erebusDir = file("${rootDir}/Erebus") -// tool versions -def antlrVersion = '4.11.1' -def jdkVersion = '22.0.1' -def jdkBuild = '8' +//-------------------------------------------------------------------------------- +// Configuration phase code +// +// Check that we are in the correct environment +def repoHomeVar = System.getenv('REPO_HOME') +def projectVar = System.getenv('PROJECT') +if( + !repoHomeVar + || !projectVar +){ + println "REPO_HOME or PROJECT not set, has 'env_pm' or 'env_dev' been sourced?" + throw new GradleException("Bailing due to not being able to make sense of the environment.") +} +if( + projectVar != "GQL_to_Cypher" + && projectVar != "GQL_to_Cypher_PM" +){ + println "Expected project 'GQL_to_Cypher', or 'GQL_to_Cypher_PM' but found '${projectVar}'." + throw new GradleException("Bailing due to apparently being in the wrong project.") +} -// The urls for upstream files -def antlrUrl = "https://www.antlr.org/download/antlr-${antlrVersion}-complete.jar" -def jdkUrl = "https://github.com/adoptium/temurin22-binaries/releases/download/jdk-${jdkVersion}%2B${jdkBuild}/OpenJDK22U-jdk_x64_linux_hotspot_${jdkVersion}_${jdkBuild}.tar.gz" +//-------------------------------------------------------------------------------- +// build +// + +task preface { + doLast { + println "================================================================================" + println "installing tools .." + } +} task installAntlr { + dependsOn preface doLast { - def antlrJar = file("${upstreamDir}/antlr-${antlrVersion}-complete.jar") - def antlrExecutorJar = file("${executorDir}/antlr-${antlrVersion}-complete.jar") + def antlrJar_FN = "antlr-${antlrVersion}-complete.jar" + def antlrJar_FP = file("${upstreamDir}/${antlrJar_FN}") + def antlrExecutorJar_FP = file("${executorDir}/${antlrJar_FN}") mkdir(upstreamDir) mkdir(executorDir) - if (!antlrJar.exists()) { + if (!antlrJar_FP.exists()) { println "Downloading ANTLR..." - new URL(antlrUrl).withInputStream { i -> antlrJar.withOutputStream { it << i } } + new URL(antlrUrl).withInputStream { i -> antlrJar_FP.withOutputStream { it << i } } } - if (!antlrExecutorJar.exists()) { + if (!antlrExecutorJar_FP.exists()) { copy { - from antlrJar + from antlrJar_FP into executorDir } println "ANTLR installed in executor." } else { println "ANTLR already installed." } + project.ext.ANTLRjar = antlrExecutorJar_FP.absolutePath + } } task installJava { + dependsOn preface doLast { def jdkTar = file("${upstreamDir}/jdk-${jdkVersion}.tar.gz") def jdkDir = file("${toolDir}/jdk-${jdkVersion}+${jdkBuild}") - + mkdir(upstreamDir) mkdir(toolDir) @@ -77,7 +113,7 @@ task installJava { if (!javaHome.exists()) { throw new GradleException("JDK extraction failed.") } - + // variables saved for use in subproject build scripts project.ext.javaHome = jdkDir @@ -90,14 +126,9 @@ task installTools { dependsOn installAntlr, installJava } -// Ensure all subprojects depend on the tool installation -subprojects { - afterEvaluate { project -> - project.tasks.each { task -> - task.dependsOn ':installTools' - } - } -} +//-------------------------------------------------------------------------------- +// additional tool install targets +// task cleanTool { description = 'Cleans the installed tools (but not upstream files)' @@ -123,7 +154,6 @@ task cleanTool { task cleanErebus { description = "Cleans the project level temporary directory 'Erebus'" - doLast { if (erebusDir.exists()) { erebusDir.eachFile { file -> diff --git a/developer/#build.gradle# b/developer/#build.gradle# new file mode 100644 index 0000000..35450ad --- /dev/null +++ b/developer/#build.gradle# @@ -0,0 +1,696 @@ +//--------------------------------------------------------------------------------- +// globals + +// string comprehension includes for regular expressions +def base = "[a-zA-Z0-9_-]+" +def ext = "[a-zA-Z0-9_-]+$" +def name = "${base}\\.${ext}" +def path = ".+/${name}" + +//-------------------------------------------------------------------------------- +// Import variables from the environment +// + +def env = [:] + +// Required shell environment variables +def varName_List = [ + 'REPO_HOME', + 'PROJECT', + 'ENV_BUILD_VERSION', + 'DEVELOPER_HOME' +] +varName_List.each { varName -> + def value = System.getenv(varName) ?: "" + env[varName] = value +} + +// Optional shell environment variables +def varNameOptional_List = [ + 'CLASSPATH' +] +varNameOptional_List.each { varName -> + def value = System.getenv(varName) ?: "" + env[varName] = value +} + +env.CLASSPATH += ":${env.ANTLR_JAR}" + +//-------------------------------------------------------------------------------- +// PM installed tools to be used +// these should be added to the project object by the installer, and taken +// from the project object here. +// +// Tools used (set by project manager: JAVA_HOME, ANTLR_JAR, DEVELOPER_HOME) +def JAVA_COMP_FP = "${env.JAVA_HOME}/bin/javac" // Java compiler path +def JAVA_INTERP_FP = "${env.JAVA_HOME}/bin/java" // Java interpreter path +def JAVA_ARCHIVE_FP = "${env.JAVA_HOME}/bin/jar" // Java archive tool path + + +//-------------------------------------------------------------------------------- +// Directory structure +// + +def dir_map = [ + ,'EXECUTOR_IN' : 'executor/' + ,'ANTLR_IN_LEAF' : 'ANTLR/' + ,'ANTLR_OUT' : 'javac/ANTLR/' + ,'ANTLR_OUT_PARENT' : 'javac/' + ,'JAVA_COMP_IN' : 'javac/' + ,'JAVA_COMP_IN_LEAF': 'javac/leaf/' + ,'JAVA_COMP_IN_ANTLR' : 'javac/ANTLR/' + ,'JAVA_COMP_IN_SYN' : 'javac/synthesized/' + ,'JAVA_COMP_OUT' : 'jvm/' + ,'JVM_IN' : 'jvm/' + ,'TEMP' : 'Erebus/' +] + +// Update CLASSPATH +env.CLASSPATH += ":${dir_map.JVM_IN}" + +// Construct JAVA_COMP_IN_LIST dynamically and add to dp map +dir_map.JAVA_COMP_IN_LIST = + "${dir_map.JAVA_COMP_IN_LEAF}" + + ":${dir_map.JAVA_COMP_IN_ANTLR}" + + ":${dir_map.JAVA_COMP_IN_SYN}" + +println "CLASSPATH: ${env.CLASSPATH}" +println "JAVA_COMP_IN_LIST: ${dir_map.JAVA_COMP_IN_LIST}" + + +// Subroutine to print missing environment variables list message +def printMissingVars(missingVars) { + if (missingVars.isEmpty()) { + // Print nothing if the list is empty + return + } else if (missingVars.size() == 1) { + println "This environment variable was not set: ${missingVars[0]}" + } else { + println "These environment variables were not set: ${missingVars.join(', ')}" + } +} + +task preface { + dependsOn ':installTools' + doFirst { + + // Environment variable checks + def error_missing = false + def error_project = false + def missingVars = [] // To collect missing environment variables + varName_List.each { varName -> + if (!env[varName]) { + error_missing = true + missingVars << varName + } + } + printMissingVars(missingVars) + if (env.PROJECT != "GQL_to_Cypher") { + error_project = true + println "Expected project 'GQL_to_Cypher', but found '${env.PROJECT}'." + } + if (error_missing || error_project) { + throw new GradleException("Bailing due to missing environment variables.") + } + } + doLast { + println "================================================================================" + println "Building project .." + } +} + +/*-------------------------------------------------------------------------------- + Dependency Graph + + A node is a property list. + + A node label is a relative path to a file, or a symbolic value. + + We either a) put nodes in a map that key on the node label then return the node, + or b) we use functions that recognize the node label and return a property list. + The `lookup(node-label)` function combines all methods to find the node. + + A will formed node will have these properties: label and type. + + A node without a 'neighbor' property is said to be a 'leaf_node'. + + `path` type nodes have a `build` property. +*/ + +//--------------------------------------- +// some helpers + +def node_type_set = ['symbol' ,'path'] as Set +def node_mark_set = ['cycle_member' ,'malformed' ] as Set + +/* + */ + +def well_formed_q = { node -> + def form_error_set = [] as Set + + if( + !node.label || node.label.length() == 0 + ) + form_error_set << 'no_node_label' + + if( + !node.type || !(node.type in node_type_set) + ) + form_error_set << 'no_node_type' + + if( + node.neighbor + && !(node.neighbor instanceof List) + ) + form_error_set << 'neighbor_not_a_list' + + if( + node.type == "path" + && (!node.build || !(node.build instanceof Closure)) + ) + form_error_set << 'no_build_code' + + if( + node.type == "symbol" + && node.build + ) + form_error_set << 'symbol_has_build_code' + + if( + node.mark + && (node.mark instance of Set) + && ! (node.mark.every {it in node_mark_set}) + ) + form_error_set << 'unregistered_mark' + + return form_error_set +} +def is_leaf_q = { node -> !node.neighbor || node.neighbor.isEmpty() } +def is_marked_q = { node -> node.mark && !node.mark.isEmpty() } + + +/*---------------------------------------- + maps node label -> node + + The keys are the node labels. They are formally added as the value to the + `label` property by the lookup function. + + Each leaf node must have a node definition. All path labels that lead to the + `javac/leaf` directory are recognized as leaf nodes. A leaf node has a + `type` property value of `leaf`, and they can be added to the map at different + file paths. + +*/ + +def node_map = [ + + "ANTLR_OUT_FL" : [ + ,type: "symbol" + ,neighbor: ["${dir_map.EXECUTOR_IN}/ANTLR_OUT_FL"] + ] + + ,"RuleNameList" : [ + ,type: "symbol" + ,neighbor: ["${dir_map.EXECUTOR_IN}/RuleNameList"] + ] + + ,"RuleNameListRegx" : [ + ,type: "symbol" + ,neighbor: ["${dir_map.EXECUTOR_IN}/RuleNameListRegx"] + ] + + ,"Synthesize_SyntaxAnnotate" : [ + ,type: "symbol" + ,neighbor: [ + "${dir_map.JAVA_COMP_IN_LEAF}/StringUtils.java" + ,"${dir_map.EXECUTOR_IN}/Synthesize_SyntaxAnnotate" + ] + ] + + ,"Synthesize_SyntaxAnnotate_PrintVisitor" : [ + ,type: "symbol" + ,neighbor: [ + "${dir_map.JAVA_COMP_IN_LEAF}/StringUtils.java" + ,"${dir_map.JAVA_COMP_IN_LEAF}/Synthesize_SyntaxAnnotate_PrintVisitorMethod.java" + ,"${dir_map.EXECUTOR_IN}/Synthesize_SyntaxAnnotate_PrintVisitor" + ] + ] + + ,"Synthesize_SyntaxAnnotate_PrintVisitorMethod" : [ + ,type: "symbol" + ,neighbor: [ + "${dir_map.JAVA_COMP_IN_LEAF}/StringUtils.java" + ,"${dir_map.EXECUTOR_IN}/Synthesize_SyntaxAnnotate_PrintVisitorMethod" + ] + ] +] + +//---------------------------------------- +// the following are recognizer functions that return node definitions. + +// any leaf node located in javac/leaf +def node_leaf_f(node_label) { + def leafNodePattern = ~/${dir_map['JAVA_COMP_IN_LEAF']}(.*)/ + def match = node_label =~ leafNodePattern + if (!match) { + return [status: "no_match"] + } + def baseName = match[0][1] + + def leafFilePath = "${dir_map['JAVA_COMP_IN_LEAF']}${baseName}" + def leafFile = new File(leafFilePath) + if (!leafFile.exists()) { + return [status: "no_match"] + } + + return [ + status: "matched" + ,label: node_label + ,type: "leaf" + ,neighbor: [] + ] +} + +// any shell wrapper (for wrapping a name corresponding .jar file) +def node_executor_f(node) { + + def match = node =~ /^(executor\/)(${base})$/ + if (!match) { + return [status: "no_match"] + } + def baseName = match[0][2] + + def jarFilePath = "${dir_map['JVM_IN']}${baseName}.jar" + def wrapperFilePath = "${dir_map['EXECUTOR_IN']}${baseName}" + + def jarFile = new File(jarFilePath) + if (!jarFile.exists()) { + return [status: "no_match"] + } + + return [ + status: "matched" + ,label: node + ,type: "path" + ,neighbor: [jarFilePath] + ,build: { node, neighbor -> + + // The script for wrapping the jar file: + def wrapper = """ + #!/usr/bin/env bash + ${dir_map['JAVA_INTERP']} -cp \${CLASSPATH}:${dir_map['JVM_IN']}:${dir_map['JVM_IN']}/${baseName}.jar ${baseName} \\\$\\@ + """ + + new File(wrapperFilePath).withWriter('UTF-8') { writer -> + writer.write(wrapper) + } + + println "Creating executable wrapper script for ${baseName} in executor directory." + "chmod +x ${wrapperFilePath}".execute().text + } + ] +} + +// any antlr output java file +def node_grammar_f(node) { + + def match = node =~ /^(${dir_map['ANTLR_OUT']})(${base})(Lexer|Parser|BaseListener|Listener|BaseVisitor|Visitor)\.java$/ + if( !match ){ + return [status: "no_match"] + } + + def grammarName = match[0][2] + def outputType = match[0][3] + + def grammarFilePath = "${dir_map['ANTLR_IN_LEAF']}${grammarName}.g4" + def grammarFile = new File(grammarFilePath) + + if( !grammarFile.exists() ){ + return [status: "no_match"] + } + + return [ + status: "matched" + ,label: node + ,type: "path" + ,neighbor: [grammarFilePath] + ] +} + +// any class file (built from a name corresponding .java file) +def node_class_f(node) { + + def match = node =~ /^(${dir_map['JAVA_COMP_OUT']})(${base})\.class$/ + if( !match ){ + return [status: "no_match"] + } + + def baseName = match[0][2] + def javaFilePath = "${dir_map['JAVA_COMP_IN_LEAF']}${baseName}.java" + def javaFile = new File(javaFilePath) + + if( !javaFile.exists() ){ + return [status: "no_match"] + } + + return [ + status: "matched" + ,label: node + ,type: "path" + ,neighbor: [javaFilePath] + ] +} + +// any jar file (built from a corresponding class file) +def node_jar_f(node) { + + // Use the symbolic name and base patterns + def match = node =~ /^(${dir_map['JAVA_COMP_OUT']})(${base})\.jar$/ + + if( !match ){ + return [status: "no_match"] + } + + def baseName = match[0][2] + def classFilePath = "${dir_map['JAVA_COMP_OUT']}${baseName}.class" + def classFile = new File(classFilePath) + + if( !classFile.exists() ){ + return [status: "no_match"] + } + + return [ + status: "matched" + ,label: node + ,type: "path" + ,neighbor: [classFilePath] + ,build: { node, neighbor -> + println "Building jar for ${baseName}" + def command = "${ext.javaHome}/bin/jar cf ${baseName}.jar -C ${dir_map['JAVA_COMP_OUT']} ${baseName}.class" + return command.execute().text; + } + ] +} + +// list of the recognizer functions +def node_f_list = [ + node_leaf_f + ,node)executor_f + ,node_grammar_f + ,node_class_f + ,node_jar_f +] + + +// combines the map and recognizer functions into a single lookup +def lookup(node_label, verbose = false){ + // the actual lookup + def lookup_node = node_map[node_label] + if( lookup_node ){ + lookup_node.label = node_label + } else { + def match_result + for( func in node_f_list ){ + match_result = func(node_label) + if( match_result.status == "matched" ){ + lookup_node = match_result + break + } + } + } + + if( !lookup_node ){ + if( verbose ) println "Lookup error: Node ${node_label} could not be found." + return null + } + + // If we already marked this one as malformed, do not check it again + // This is done primarily to avoid duplicate error messages, as it isn't a big + // performance win. + if( lookup_node.mark && ('malformed' in lookup_node.mark) ) return null + + // Check if the node is well formed + def form_errors = well_formed_q(lookup_node) + if( !form_errors.isEmpty() ){ + // Mark the node as malformed + if( !lookup_node.mark ) lookup_node.mark = [] as Set + lookup_node.mark << 'malformed' + + // Tell the user + if( verbose ){ + println "Lookup error: Node ${node_label} is malformed due to: ${form_errors.join(', ')}" + } + + // Refuse to return a malformed node + return null + } + + return lookup_node +} + +/*-------------------------------------------------------------------------------- + is_acyclic_q checks a dependency graph for cycles + + given a dependency graph. Returns either 'given_argument_not_accepted', + `cycles` or `acyclic`. + + marks the nodes that are in cycles. + + There is a call function and a helper function + +*/ + +def is_acyclic_q_descend(path_stack ,boolean verbose = true){ + def local_path = path_stack.collect{ node_label -> lookup(node_label) } + def local_node = local_path[-1] + def cycle_start_index + + do{ + // Check for a cycle in the local path + cycle_start_index = local_path[0..-2].findIndexOf{ node -> node.label == local_node.label } + if( cycle_start_index != -1 ){ // Cycle detected + if( verbose ) print "is_acyclic_q:: dependency cycle found:" + local_path[cycle_start_index..-1].each{ cycle_node -> + if( verbose ) print " ${cycle_node.label}" + cycle_node.mark = cycle_node.mark ?: [] as Set // Initialize mark set if needed + cycle_node.mark << 'cycle_member' + } + if( verbose ) println "" + // Pop off the stack above the cycle start, so that search can continue if desired + path_stack = path_stack[0..cycle_start_index] + return 'cycle_found' + } + + // Put this test after the cycle detector in case another definition with + // the same label had children and participated in a cycle. + if( local_node.neighbor.isEmpty() ) return 'leaf_node' + + // Descend further into the tree. + path_stack << local_node.neighbor.clone() + local_node = lookup(local_node.neighbor[0]) + local_path << local_node + }while( true ) +} + +def is_acyclic_q(root_node_labels ,boolean verbose = true){ + // We are optimistic. + def return_value = 'acyclic' + + // Initialize the DFS tree iterator. + def path_stack = [] + path_stack << root_node_labels.clone() + + // iterate over left side tree descent, not ideal as it starts at the + // root each time, but avoids complexity in the cycle detection logic. + do{ + def result = is_acyclic_q_descend(path_stack ,verbose) + if(result == 'cycle_found') return_value = result + + // increment the iterator to the next leftmost path + def top_list = path_stack[-1] + top_list.remove(0) + if( top_list.isEmpty() ) path_stack.pop() + + }while( !path_stack.isEmpty() ) + + return return_value +} + +// LocalWords: FN FPL DN DNL RuleNameListRegx RuleNameList PrintVisitorMethod +// LocalWords: PrintVisitor SyntaxAnnotate + +/*-------------------------------------------------------------------------------- + run the build scripts + depends upon is_acyclic having already marked up the graph. + +Initialization: +* visited: A set to keep track of visited nodes. +* build_order: A list to store the nodes in the order they should be built. + +Depth-First Search (DFS): +* The dfs function performs a depth-first traversal of the graph. +* It looks up the node using the lookup function. +* If the node has already been visited, it returns. +* Otherwise, it marks the node as visited and recursively visits its neighbors. +* After visiting all neighbors, it adds the node to the build_order list. + +Build Script Execution: +* After the DFS traversal, the build_order list is reversed to ensure that leaf nodes are built first. +* The build scripts for each node are executed in the correct order. + +-- add date checks + +*/ + +def run_build_scripts(root_node_labels ,boolean verbose = false){ + def visited = [] as Set + def build_order = [] + + def dfs(node_label){ + def node = lookup(node_label ,verbose) + if( !node ) return + + if( node.label in visited ) return + visited << node.label + + node.neighbor.each{ neighbor_label -> + dfs(neighbor_label) + } + + build_order << node + } + + root_node_labels.each{ root_label -> + dfs(root_label) + } + + build_order.reverse().each{ node -> + if( node.build ){ + println "Running build script for ${node.label}" + node.build(node ,node.neighbor) + } + } +} + +// Example usage +def root_node_labels = ["ANTLR_OUT_FL" ,"RuleNameList"] +run_build_scripts(root_node_labels ,true) + + +def run_build_scripts(root_node_labels ,boolean verbose = false){ + if( root_node_labels.isEmpty() ) return + + def visited = [] as Set + def build_order = [] + def stack = [] + + root_node_labels.each{ root_label -> + stack << root_label + } + + do{ + def node_label = stack.pop() + def node = lookup(node_label ,verbose) + if( !node ) continue + + if( node.label in visited ) continue + visited << node.label + + node.neighbor.each{ neighbor_label -> + stack << neighbor_label + } + + build_order << node + }while( !stack.isEmpty() ) + + build_order.reverse().each{ node -> + if( node.build ){ + println "Running build script for ${node.label}" + node.build(node ,node.neighbor) + } + } +} + +-------------------- + +def should_build(node, verbose = false){ + if( node.mark && ('malformed' in node.mark || 'cycle_member' in node.mark) ){ + if( verbose ) println "Skipping build for ${node.label} due to errors or cycle membership." + return false + } + + def nodeFile = new File(node.label) + if( !nodeFile.exists() ){ + if( verbose ) println "Node file ${node.label} does not exist." + return false + } + + def nodeLastModified = nodeFile.lastModified() + def shouldBuild = false + + node.neighbor.each{ neighbor_label -> + def neighborNode = lookup(neighbor_label, verbose) + if( !neighborNode || neighborNode.type != 'path' ){ + if( verbose ) println "Dependency ${neighbor_label} is missing or not a path type." + return false + } + + def neighborFile = new File(neighbor_label) + if( !neighborFile.exists() ){ + if( verbose ) println "Dependency file ${neighbor_label} does not exist." + return false + } + + def neighborLastModified = neighborFile.lastModified() + if( neighborLastModified > nodeLastModified ){ + shouldBuild = true + } + } + + return shouldBuild +} + +def run_build_scripts(root_node_labels ,boolean verbose = false){ + if( root_node_labels.isEmpty() ) return + + def visited = [] as Set + def build_order = [] + def stack = [] + + root_node_labels.each{ root_label -> + stack << root_label + } + + do{ + def node_label = stack.pop() + def node = lookup(node_label ,verbose) + if( !node ) continue + + if( node.label in visited ) continue + visited << node.label + + node.neighbor.each{ neighbor_label -> + stack << neighbor_label + } + + build_order << node + }while( !stack.isEmpty() ) + + build_order.reverse().each{ node -> + if( node.build && should_build(node ,verbose) ){ + println "Running build script for ${node.label}" + node.build(node ,node.neighbor) + } + } +} + +// Example usage +def root_node_labels = ["ANTLR_OUT_FL" ,"RuleNameList"] +run_build_scripts(root_node_labels ,true) + + + +// Example usage +def root_node_labels = ["ANTLR_OUT_FL" ,"RuleNameList"] +run_build_scripts(root_node_labels ,true) diff --git a/developer/Lethe/buid_from_leaf.gradle b/developer/Lethe/buid_from_leaf.gradle new file mode 100644 index 0000000..c23a159 --- /dev/null +++ b/developer/Lethe/buid_from_leaf.gradle @@ -0,0 +1,68 @@ +/*-------------------------------------------------------------------------------- + build_from_leaf + + Accepts `build_result_fpl` and `leaf_dependency_fpl` (fpl = file path list). + Typically, `build_result_fpl` will hold a single item, representing the target + to be built, and `leaf_dependency_fpl` contains the leaf dependencies for that + target. + + This function checks the build status of the target relative to its dependencies. + + It returns a dictionary with the following fields based on the result of the check: + + type -> A string indicating the result type: + 'no_build_target_specified', 'non_existent_build_target', + 'up_to_date', or 'need_to_build'. + + message -> A string message describing the result (optional). + + missing -> If type is 'non_existent_build_target', this field contains the + list of missing build targets. + + build_result -> If type is 'need_to_build', this field contains the list + of build result filenames that need to be rebuilt. +*/ + +def build_from_leaf(build_result_fpl ,leaf_dependency_fpl) { + + if( !build_result_fpl || build_result_fpl.isEmpty() ){ + return [ + type: "no_build_target_specified" + ,message: "No build target specified" + ] + } + + // Check if any build result file does not exist + def missing_build_fpl = build_result_fpl.findAll{ file_path -> + !new File(file_path).exists() + } + + if( !missing_build_fpl.isEmpty() ){ + return [ + type: "non_existent_build_target" + ,message: "Missing build targets" + ,missing: missing_build_fpl + ] + } + + // Check if any dependency is newer than the build result files + def all_up_to_date_bool = build_result_fpl.every{ result_file_path -> + def result_file = new File(result_file_path) + !leaf_dependency_fpl.any{ dep_path -> + new File(dep_path).lastModified() > result_file.lastModified() + } + } + + if( all_up_to_date_bool ){ + return [ + type: "up_to_date" + ,message: "Build result is up to date" + ] + }else{ + return [ + type: "need_to_build" + ,message: "Build result needs to be rebuilt" + ,build_result: build_result_fpl + ] + } +} diff --git a/developer/Lethe/build.gradle b/developer/Lethe/build.gradle index b5ab4a6..e53add0 100644 --- a/developer/Lethe/build.gradle +++ b/developer/Lethe/build.gradle @@ -1,8 +1,23 @@ task setup { doLast { - def dirs = ["$(ANTLR_IN_PRIMARY_DIR)", "$(JAVA_COMP_IN_PRIMARY_DIR)", "$(JVM_IN_DIR)", "$(EXECUTOR_IN_DIR)", "test", "deprecated", "experiment", "ologist", "temporary"] + def dirs = [ + "$(ANTLR_IN_PRIMARY_DIR)" + ,"$(JAVA_COMP_IN_DIR)" + ,"$(JAVA_COMP_IN_PRIMARY_DIR)" + ,"$(JAVA_COMP_IN_ANTLR_DIR)" + ,"$(JAVA_COMP_IN_SYN_DIR)" + ,"$(JVM_IN_DIR)" + ,"$(EXECUTOR_IN_DIR)" + ,"test" + ,"deprecated" + ,"experiment" + ,"ologist" + ,"temporary" + ] dirs.each { dir -> - mkdir dir + if (!file(dir).exists()) { + mkdir dir + } } } } @@ -54,38 +69,3 @@ tasks.withType(Exec) { println "Executing $name" } } - - ----------------------- -// Function to compile .java files to .class files -def compileJava(source, target) { - tasks.create(name: "compile${source}", type: Exec) { - commandLine '$(JAVA_COMP)', '-d', '$(JAVA_COMP_OUT_DIR)', '-sourcepath', '$(JAVA_COMP_IN_DL)', source - doLast { - println "Compiled ${source} to ${target}" - } - } -} - -// Function to create .jar files from .class files -def createJar(source, target) { - tasks.create(name: "jar${source}", type: Exec) { - commandLine '$(JAVA_ARCHIVE)', 'cf', target, '-C', '$(JAVA_COMP_OUT_DIR)', source - doLast { - println "Created ${target}" - } - } -} - -// Function to create wrapper scripts from .jar files -def createWrapper(source, target) { - tasks.create(name: "wrapper${source}", type: Exec) { - doLast { - def script = new File(target) - script.text = "#!/usr/bin/env bash\n$(JAVA_INTERP) -cp ${CLASSPATH}:${JVM_IN_DP}:${JVM_IN_DP}/${source}.jar ${source} \$@" - script.setExecutable(true) - println "Created program ${target}" - } - } -} - diff --git a/developer/Lethe/makefile-project.mk b/developer/Lethe/makefile-project.mk index ea8a0f3..7f8d6ef 100644 --- a/developer/Lethe/makefile-project.mk +++ b/developer/Lethe/makefile-project.mk @@ -46,7 +46,7 @@ Synthesize_SyntaxAnnotate_PrintVisitor:\ $(EXECUTOR_IN_DIR)/Synthesize_SyntaxAnnotate_PrintVisitor Synthesize_SyntaxAnnotate:\ - $(JAVA_COMP_IN_PRIMARY_DIR)/StringUtils.java\ + $(JAVA_COMP_IN_PRIMARY_DIR)/StringUtils.java\https://github.com/Thomas-Walker-Lynch/GQL_to_Cypher $(EXECUTOR_IN_DIR)/Synthesize_SyntaxAnnotate #----------------------------------------------- @@ -236,6 +236,7 @@ $(JAVA_COMP_OUT_DIR)/%.class: $(JAVA_COMP_IN_PRIMARY_DIR)/%.java .PRECIOUS: $(JAVA_COMP_OUT_DIR)/%.jar # make .jar from .class file +$(JAVA_COMP_OUT_DIR)/%.class: $(JAVA_COMP_IN_PRIMARY_DIR)/%.java $(JAVA_COMP_OUT_DIR)/%.jar: $(JAVA_COMP_OUT_DIR)/%.class @echo "Building $*..." $(JAVA_ARCHIVE) cf $@ -C $(JAVA_COMP_OUT_DIR) $*.class diff --git a/developer/build.gradle b/developer/build.gradle index e53add0..ffc383d 100644 --- a/developer/build.gradle +++ b/developer/build.gradle @@ -1,71 +1,617 @@ -task setup { - doLast { - def dirs = [ - "$(ANTLR_IN_PRIMARY_DIR)" - ,"$(JAVA_COMP_IN_DIR)" - ,"$(JAVA_COMP_IN_PRIMARY_DIR)" - ,"$(JAVA_COMP_IN_ANTLR_DIR)" - ,"$(JAVA_COMP_IN_SYN_DIR)" - ,"$(JVM_IN_DIR)" - ,"$(EXECUTOR_IN_DIR)" - ,"test" - ,"deprecated" - ,"experiment" - ,"ologist" - ,"temporary" - ] - dirs.each { dir -> - if (!file(dir).exists()) { - mkdir dir +//--------------------------------------------------------------------------------- +// globals + +// string comprehension includes for regular expressions +def base = "[a-zA-Z0-9_-]+" +def ext = "[a-zA-Z0-9_-]+$" +def name = "${base}\\.${ext}" +def path = ".+/${name}" + +//-------------------------------------------------------------------------------- +// Import variables from the environment +// + +def env = [:] + +// Required shell environment variables +def varName_List = [ + 'REPO_HOME', + 'PROJECT', + 'ENV_BUILD_VERSION', + 'DEVELOPER_HOME' +] +varName_List.each { varName -> + def value = System.getenv(varName) ?: "" + env[varName] = value +} + +// Optional shell environment variables +def varNameOptional_List = [ + 'CLASSPATH' +] +varNameOptional_List.each { varName -> + def value = System.getenv(varName) ?: "" + env[varName] = value +} + +env.CLASSPATH += ":${env.ANTLR_JAR}" + +//-------------------------------------------------------------------------------- +// PM installed tools to be used +// these should be added to the project object by the installer, and taken +// from the project object here. +// +// Tools used (set by project manager: JAVA_HOME, ANTLR_JAR, DEVELOPER_HOME) +def JAVA_COMP_FP = "${env.JAVA_HOME}/bin/javac" // Java compiler path +def JAVA_INTERP_FP = "${env.JAVA_HOME}/bin/java" // Java interpreter path +def JAVA_ARCHIVE_FP = "${env.JAVA_HOME}/bin/jar" // Java archive tool path + + +//-------------------------------------------------------------------------------- +// Directory structure +// + +def dir_map = [ + ,'EXECUTOR_IN' : 'executor/' + ,'ANTLR_IN_LEAF' : 'ANTLR/' + ,'ANTLR_OUT' : 'javac/ANTLR/' + ,'ANTLR_OUT_PARENT' : 'javac/' + ,'JAVA_COMP_IN' : 'javac/' + ,'JAVA_COMP_IN_LEAF': 'javac/leaf/' + ,'JAVA_COMP_IN_ANTLR' : 'javac/ANTLR/' + ,'JAVA_COMP_IN_SYN' : 'javac/synthesized/' + ,'JAVA_COMP_OUT' : 'jvm/' + ,'JVM_IN' : 'jvm/' + ,'TEMP' : 'Erebus/' +] + +// Update CLASSPATH +env.CLASSPATH += ":${dir_map.JVM_IN}" + +// Construct JAVA_COMP_IN_LIST dynamically and add to dp map +dir_map.JAVA_COMP_IN_LIST = + "${dir_map.JAVA_COMP_IN_LEAF}" + + ":${dir_map.JAVA_COMP_IN_ANTLR}" + + ":${dir_map.JAVA_COMP_IN_SYN}" + +println "CLASSPATH: ${env.CLASSPATH}" +println "JAVA_COMP_IN_LIST: ${dir_map.JAVA_COMP_IN_LIST}" + + +// Subroutine to print missing environment variables list message +def printMissingVars(missingVars) { + if (missingVars.isEmpty()) { + // Print nothing if the list is empty + return + } else if (missingVars.size() == 1) { + println "This environment variable was not set: ${missingVars[0]}" + } else { + println "These environment variables were not set: ${missingVars.join(', ')}" + } +} + +task preface { + dependsOn ':installTools' + doFirst { + + // Environment variable checks + def error_missing = false + def error_project = false + def missingVars = [] // To collect missing environment variables + varName_List.each { varName -> + if (!env[varName]) { + error_missing = true + missingVars << varName } } + printMissingVars(missingVars) + if (env.PROJECT != "GQL_to_Cypher") { + error_project = true + println "Expected project 'GQL_to_Cypher', but found '${env.PROJECT}'." + } + if (error_missing || error_project) { + throw new GradleException("Bailing due to missing environment variables.") + } + } + doLast { + println "================================================================================" + println "Building project .." } } -task tool(type: Exec) { - dependsOn setup - commandLine '$(BIN_MAKE)', '-f', '$(EXECUTOR_IN_DIR)/makefile-tool.mk', '-$(MAKEFLAGS)', 'all' +/*-------------------------------------------------------------------------------- + Dependency Graph + + A node is a property list. + + A node label is a relative path to a file, or a symbolic value. + + We either a) put nodes in a map that key on the node label then return the node, + or b) we use functions that recognize the node label and return a property list. + The `lookup(node-label)` function combines all methods to find the node. + + A will formed node will have these properties: label and type. + + A node without a 'neighbor' property is said to be a 'leaf_node'. + + `path` type nodes have a `build` property. +*/ + +//--------------------------------------- +// some helpers + +def node_type_set = ['symbol' ,'path'] as Set +def node_mark_set = ['cycle_member' ,'malformed' ] as Set + +/* + */ + +def well_formed_q = { node -> + def form_error_set = [] as Set + + if( + !node.label || node.label.length() == 0 + ) + form_error_set << 'no_node_label' + + if( + !node.type || !(node.type in node_type_set) + ) + form_error_set << 'no_node_type' + + if( + node.neighbor + && !(node.neighbor instanceof List) + ) + form_error_set << 'neighbor_not_a_list' + + if( + node.type == "path" + && (!node.build || !(node.build instanceof Closure)) + ) + form_error_set << 'no_build_code' + + if( + node.type == "symbol" + && node.build + ) + form_error_set << 'symbol_has_build_code' + + if( + node.mark + && (node.mark instance of Set) + && ! (node.mark.every {it in node_mark_set}) + ) + form_error_set << 'unregistered_mark' + + return form_error_set } +def is_leaf_q = { node -> !node.neighbor || node.neighbor.isEmpty() } +def is_marked_q = { node -> node.mark && !node.mark.isEmpty() } + + +/*---------------------------------------- + maps node label -> node + + The keys are the node labels. They are formally added as the value to the + `label` property by the lookup function. + + Each leaf node must have a node definition. All path labels that lead to the + `javac/leaf` directory are recognized as leaf nodes. A leaf node has a + `type` property value of `leaf`, and they can be added to the map at different + file paths. + +*/ + +def node_map = [ + + "ANTLR_OUT_FL" : [ + ,type: "symbol" + ,neighbor: ["${dir_map.EXECUTOR_IN}/ANTLR_OUT_FL"] + ] + + ,"RuleNameList" : [ + ,type: "symbol" + ,neighbor: ["${dir_map.EXECUTOR_IN}/RuleNameList"] + ] + + ,"RuleNameListRegx" : [ + ,type: "symbol" + ,neighbor: ["${dir_map.EXECUTOR_IN}/RuleNameListRegx"] + ] + + ,"Synthesize_SyntaxAnnotate" : [ + ,type: "symbol" + ,neighbor: [ + "${dir_map.JAVA_COMP_IN_LEAF}/StringUtils.java" + ,"${dir_map.EXECUTOR_IN}/Synthesize_SyntaxAnnotate" + ] + ] + + ,"Synthesize_SyntaxAnnotate_PrintVisitor" : [ + ,type: "symbol" + ,neighbor: [ + "${dir_map.JAVA_COMP_IN_LEAF}/StringUtils.java" + ,"${dir_map.JAVA_COMP_IN_LEAF}/Synthesize_SyntaxAnnotate_PrintVisitorMethod.java" + ,"${dir_map.EXECUTOR_IN}/Synthesize_SyntaxAnnotate_PrintVisitor" + ] + ] + + ,"Synthesize_SyntaxAnnotate_PrintVisitorMethod" : [ + ,type: "symbol" + ,neighbor: [ + "${dir_map.JAVA_COMP_IN_LEAF}/StringUtils.java" + ,"${dir_map.EXECUTOR_IN}/Synthesize_SyntaxAnnotate_PrintVisitorMethod" + ] + ] +] + +//---------------------------------------- +// the following are recognizer functions that return node definitions. + +// any leaf node located in javac/leaf +def node_leaf_f(node_label) { + def leafNodePattern = ~/${dir_map['JAVA_COMP_IN_LEAF']}(.*)/ + def match = node_label =~ leafNodePattern + if (!match) { + return [status: "no_match"] + } + def baseName = match[0][1] + + def leafFilePath = "${dir_map['JAVA_COMP_IN_LEAF']}${baseName}" + def leafFile = new File(leafFilePath) + if (!leafFile.exists()) { + return [status: "no_match"] + } -task project(type: Exec) { - dependsOn tool - commandLine '$(BIN_MAKE)', '-f', '$(EXECUTOR_IN_DIR)/makefile-project.mk', '-$(MAKEFLAGS)', 'all' + return [ + status: "matched" + ,label: node_label + ,type: "leaf" + ,neighbor: [] + ] } -task clean { - doLast { - println "Use the command `clean