| | /*********************************************************************** |
---|
| | * CONFIGURABLE SETTINGS |
---|
| | * |
---|
| | * Override the following values in lecture.gradle.kts. |
---|
| | * In most cases simply adding a modified copy of the original line |
---|
| | * should do what you need. Most collections are sets to avoid accidental |
---|
| | * duplication. You can append to an existing set value by doing something |
---|
| | * like this (e.g.): |
---|
| | * |
---|
| | * extra["latexFlags"] = (extra["latexFlags"] as Set<String>) + "--barf" |
---|
| | ***********************************************************************/ |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Supported targets (unnumbered documents). |
---|
| | * Remove any targets that don't exist for this lecture. |
---|
| | * New targets must be registered in lecture.gradle.kts. |
---|
| | * The target list for numbered documents is automatically derived from |
---|
| | * this (see below). |
---|
| | */ |
---|
| | val docTargets by extra(mutableSetOf<String>( |
---|
| | "slides", |
---|
| | "handout", |
---|
| | "notes", |
---|
| | "examples" |
---|
| | )) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Command line options for LaTeX. This is a set, so duplicates will be |
---|
| | * automatically eliminated. |
---|
| | */ |
---|
| | val latexFlags by extra(mutableSetOf<String>( |
---|
| | "-shell-escape", |
---|
| | "-synctex=1", |
---|
| | "-interaction=nonstopmode", |
---|
| | "-halt-on-error", |
---|
| | "-file-line-error" |
---|
| | )) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Standard document names (unnumbered). The keys are mostly the Gradle |
---|
| | * target names in docTargets, plus "content". |
---|
| | */ |
---|
| | val docNames by extra(mutableMapOf<String, String>( |
---|
| | "content" to "lecture_content", |
---|
| | "slides" to "lecture_slides", |
---|
| | "handout" to "lecture_handout", |
---|
| | "notes" to "lecture_notes", |
---|
| | "examples" to "lecture_examples" |
---|
| | )) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Lecture number, derived from the project name (assumes the project |
---|
| | * folder is named something like "01_Lecture_Title"). |
---|
| | */ |
---|
| | val lectureNum by extra(project.name.substring(0, 2)) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Name of subdirectory in which images are kept. |
---|
| | */ |
---|
| | val imageDir by extra(layout.projectDirectory.dir("images")) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Name of subdirectory to which numbered PDFs will be written. Originally |
---|
| | * These were written into the project directory, but it seemed to completely |
---|
| | * confuse Gradle into thinking that all the input files in the project |
---|
| | * has been touched and it yelled about implicit task dependencies. See |
---|
| | * <https://docs.gradle.org/7.3.1/userguide/validation_problems.html#implicit_dependency>. |
---|
| | */ |
---|
| | val pdfDir by extra(layout.projectDirectory.dir("pdfs")) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Images and files used by the slides, handout, and notes documents. |
---|
| | * Images will be automatically mapped to imageDir (above). |
---|
| | */ |
---|
| | val slidesImages by extra(mutableSetOf<String>()) |
---|
| | val slidesFiles by extra(mutableSetOf<String>( |
---|
| | "${docNames["content"]}.tex", |
---|
| | "paper_init.tex", |
---|
| | "lecturedates.tex", |
---|
| | "doc_init.tex" |
---|
| | )) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Images and files used by the examples document. |
---|
| | * Images will be automatically mapped to imageDir (above). |
---|
| | */ |
---|
| | val examplesImages by extra(mutableSetOf<String>()) |
---|
| | val examplesFiles by extra(mutableSetOf<String>( |
---|
| | "${docNames["examples"]}.tex", |
---|
| | "paper_init.tex", |
---|
| | "lecturedates.tex", |
---|
| | "doc_init.tex" |
---|
| | )) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Images that are generated from another format. |
---|
| | * Key is the target image, value is the source image. |
---|
| | * All images will be mapped to imageDir (above). |
---|
| | */ |
---|
| | val generatedImages by extra(mutableMapOf<String, String>()) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Files and directories to be removed by the clean task. |
---|
| | * Directories are separate because FileTree only stores files :(. |
---|
| | */ |
---|
| | val cleanFiles by extra(mutableSetOf<String>( |
---|
| | "*.aux", |
---|
| | "*.dvi", |
---|
| | "*.fdb_latexmk", |
---|
| | "*.fls", |
---|
| | "*.head", |
---|
| | "*.listing", |
---|
| | "*.log", |
---|
| | "*.nav", |
---|
| | "*.out", |
---|
| | "*.pyg", |
---|
| | "*.snm", |
---|
| | "*.synctex.gz", |
---|
| | "*.tmp", |
---|
| | "*.toc", |
---|
| | "*.vrb", |
---|
| | "*.xdv" |
---|
| | )) |
---|
| | // These are regular expressions, NOT glob patterns. |
---|
| | val cleanDirs by extra(mutableSetOf<String>( |
---|
| | """_minted.*""", |
---|
| | )) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * END OF CONFIGURABLE SETTINGS |
---|
| | ***********************************************************************/ |
---|
| | |
---|
| | apply(from = file("lecture.gradle.kts")) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Supported targets (numbered documents). |
---|
| | * Automatically derived from docTargets. |
---|
| | */ |
---|
| | val numberedTargets by extra(docTargets.map { it + ".numbered" }) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Standard document names (unnumbered). |
---|
| | * Automatically derived from docNames. |
---|
| | */ |
---|
| | val numberedNames by extra(docNames.mapValues { pair -> pair.value.replace("_", "_${lectureNum}_") }) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Document dependencies. |
---|
| | * Automatically derived from slidesImages, slidesFiles, examplesImages, |
---|
| | * and examplesFiles. Images are mapped to imageDir. |
---|
| | * The slides, handout, and notes will always share the same dependencies. |
---|
| | * Only the examples will be different. |
---|
| | */ |
---|
| | val slideDependencies by extra(slidesImages.map { "${imageDir}/${it}" } + slidesFiles) |
---|
| | val docDependencies by extra(mapOf<String, List<String>>( |
---|
| | "slides" to slideDependencies, |
---|
| | "handout" to slideDependencies, |
---|
| | "notes" to slideDependencies, |
---|
| | "examples" to examplesImages.map { "${imageDir}/${it}" } + examplesFiles |
---|
| | )) |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * LaTeX plugin configuration. |
---|
| | */ |
---|
| | plugins { |
---|
| | // https://plugins.gradle.org/plugin/org.danilopianini.gradle-latex |
---|
| | id("org.danilopianini.gradle-latex") version "0.2.7" // as of 2021-12-02 |
---|
| | |
---|
| | // for debugging task dependencies |
---|
| | // https://plugins.gradle.org/plugin/org.barfuin.gradle.taskinfo |
---|
| | // id("org.barfuin.gradle.taskinfo") version "1.3.1" |
---|
| | } |
---|
| | |
---|
| | latex { |
---|
| | pdfLatexCommand.set("xelatex") |
---|
| | // Generate LaTeX build tasks for each of the targets. |
---|
| | docTargets.forEach { target -> |
---|
| | "${docNames[target]}" { |
---|
| | extraArguments = latexFlags |
---|
| | watching = docDependencies[target] as List<String> |
---|
| | } |
---|
| | } |
---|
| | } |
---|
| | |
---|
| | // Add image task dependencies to the generated LaTeX tasks. |
---|
| | // No need to check whether there are any generated images, Gradle does |
---|
| | // the right thing if the list is empty. |
---|
| | tasks.matching { it.name.startsWith("pdfLatex") }.forEach { |
---|
| | it.dependsOn(tasks.matching { task -> task.name.startsWith("images.") }) |
---|
| | } |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Tasks. |
---|
| | */ |
---|
| | defaultTasks("docs", "numbered") |
---|
| | |
---|
| | // All unnumbered documents. |
---|
| | tasks.register("docs") { |
---|
| | dependsOn(docTargets) |
---|
| | description = "Build all documents." |
---|
| | } |
---|
| | |
---|
| | // Generate tasks for each of the unnumbered document targets. |
---|
| | (docTargets as Set<String>).forEach { |
---|
| | target -> tasks.register(target) { |
---|
| | dependsOn("pdfLatex.${docNames[target]}") |
---|
| | description = "Build ${target} document." |
---|
| | } |
---|
| | } |
---|
| | |
---|
| | // All numbered documents. |
---|
| | tasks.register("numbered") { |
---|
| | dependsOn(numberedTargets) |
---|
| | description = "Create numbered versions of all documents." |
---|
| | } |
---|
| | |
---|
| | /* Generate tasks for each of the numbered document targets. |
---|
| | * Slight oddity: numberedTargets is a List not a Set because it's |
---|
| | * generated by a map() call. It's not normally configurable, so OK. |
---|
| | */ |
---|
| | (numberedTargets as List<String>).forEach { |
---|
| | target -> tasks.register<Copy>(target) { |
---|
| | val plainTarget = target.replace(".numbered", "") |
---|
| | from(tasks.getByName("pdfLatex.${docNames[plainTarget]}")) |
---|
| | into(pdfDir) |
---|
| | // The LaTeX tasks include the .aux file in their outputs. |
---|
| | // Exclude or they will also be copied into the PDFs directory. |
---|
| | exclude("*.aux") |
---|
| | rename { src -> src.replace("_", "_${lectureNum}_") } |
---|
| | description = "Build numbered version of ${plainTarget} document." |
---|
| | } |
---|
| | } |
---|
| | |
---|
| | // All generated images. |
---|
| | tasks.register("images") { |
---|
| | dependsOn(tasks.matching { it.name.startsWith("images.") }) |
---|
| | description = "Generate images" |
---|
| | } |
---|
| | |
---|
| | // Find folders matching regular expression. |
---|
| | // Adapted from <https://gist.github.com/Ethsaam/2b8dd748fcc25e6973bb>. |
---|
| | fun getListOfFolders(dir: Directory, pattern: String): Collection<File> { |
---|
| | return dir.getAsFile().listFiles({ file -> file.isDirectory() && file.name.matches(Regex(pattern)) }).toList() |
---|
| | } |
---|
| | |
---|
| | // Remove files specified in cleanFiles and directories specified |
---|
| | // in cleanDirs. |
---|
| | tasks.register<Delete>("clean") { |
---|
| | description = "Clean up intermediate files." |
---|
| | delete(fileTree(layout.projectDirectory) { include(cleanFiles) }) |
---|
| | cleanDirs.forEach { pattern -> |
---|
| | delete(getListOfFolders(layout.projectDirectory, pattern)) |
---|
| | } |
---|
| | } |
---|
| | |
---|
| | // Remove everything not under version control. This includes the .gradle |
---|
| | // directory. |
---|
| | tasks.register<Exec>("deepClean") { |
---|
| | description = "Remove all files not in Git." |
---|
| | commandLine("git", "clean", "-fXd") |
---|
| | } |
---|
| | |
---|
| | /*********************************************************************** |
---|
| | * Per-file tasks to convert images. |
---|
| | * By default these assume single-file inputs, e.g., foo.svg -> foo.pdf. |
---|
| | * If you have a file that is dependent on multiple inputs (e.g., |
---|
| | * bar.pu -> foo.pu -> foo.pdf), register an independent task in |
---|
| | * lecture.gradle.kts by copying the corresponding registration code |
---|
| | * below, substituting the actual filenames in place of the variable |
---|
| | * references like key and generatedImages[key]. Also remove the file |
---|
| | * from generatedImages to avoid a name clash. You can access properties |
---|
| | * like imageDir via project.properties (these will probably need explicit |
---|
| | * type casting). |
---|
| | */ |
---|
| | |
---|
| | // Generate per-file tasks to convert PlantUML to PDF. |
---|
| | generatedImages.filterValues { |
---|
| | it.matches(Regex(""".*\.(pu|puml|plantuml)$""")) |
---|
| | }.filterKeys { |
---|
| | it.matches(Regex(""".*\.pdf$""")) |
---|
| | }.keys |
---|
| | .forEach { key -> |
---|
| | val dst = imageDir.file(key) |
---|
| | val src = imageDir.file(generatedImages[key]) |
---|
| | tasks.register<Exec>("images.${key}") { |
---|
| | // set working directory to imageDir so that relative includes work |
---|
| | workingDir(imageDir) |
---|
| | inputs.files(src) |
---|
| | outputs.files(dst) |
---|
| | // JAVA_TOOL_OPTIONS stops Java from stealing focus on macOS. |
---|
| | environment("JAVA_TOOL_OPTIONS", "-Djava.awt.headless=true") |
---|
| | // Direct PDF from PlantUML is problematic, go via SVG instead. |
---|
| | commandLine( |
---|
| | "bash", |
---|
| | "-c", |
---|
| | "plantuml -tsvg -pipe < ${src} | inkscape --pipe --export-area-drawing --export-text-to-path --export-type=pdf --export-filename=${dst}" |
---|
| | ) |
---|
| | description = "Generate ${key} from ${generatedImages[key]}." |
---|
| | } |
---|
| | // Every PDF generation will have a different hash, so only run the |
---|
| | // task if the input is newer than the output. |
---|
| | tasks.getByPath("images.${key}").onlyIf { src.getAsFile().lastModified() > dst.getAsFile().lastModified() } |
---|
| | } |
---|
| | |
---|
| | // Generate per-file tasks to convert SVG to PDF. |
---|
| | generatedImages.filterValues { |
---|
| | it.matches(Regex(""".*\.svg$""")) |
---|
| | }.filterKeys { |
---|
| | it.matches(Regex(""".*\.pdf$""")) |
---|
| | }.keys |
---|
| | .forEach { key -> |
---|
| | val dst = imageDir.file(key) |
---|
| | val src = imageDir.file(generatedImages[key]) |
---|
| | tasks.register<Exec>("images.${key}") { |
---|
| | inputs.files(src) |
---|
| | outputs.files(dst) |
---|
| | commandLine( |
---|
| | "bash", |
---|
| | "-c", |
---|
| | "inkscape --export-area-drawing --export-text-to-path --export-type=pdf --export-filename=${dst} ${src}" |
---|
| | ) |
---|
| | description = "Generate ${key} from ${generatedImages[key]}." |
---|
| | } |
---|
| | // Every PDF generation will have a different hash, so only run the |
---|
| | // task if the input is newer than the output. |
---|
| | tasks.getByPath("images.${key}").onlyIf { src.getAsFile().lastModified() > dst.getAsFile().lastModified() } |
---|
| | } |
---|
| | |
---|
| | // Generate per-file tasks to convert R to PDF. |
---|
| | generatedImages.filterValues { |
---|
| | it.matches(Regex(""".*\.R$""")) |
---|
| | }.filterKeys { |
---|
| | it.matches(Regex(""".*\.pdf$""")) |
---|
| | }.keys |
---|
| | .forEach { key -> |
---|
| | val dst = imageDir.file(key) |
---|
| | val src = imageDir.file(generatedImages[key]) |
---|
| | tasks.register<Exec>("images.${key}") { |
---|
| | val cropped = imageDir.file(key.replace(".pdf", "-crop.pdf")) |
---|
| | inputs.files(src) |
---|
| | outputs.files(dst) |
---|
| | commandLine( |
---|
| | "bash", |
---|
| | "-c", |
---|
| | "R --no-echo --file=${src} --args ${dst} && pdfcrop ${dst} ${cropped} && mv -f ${cropped} ${dst}" |
---|
| | ) |
---|
| | description = "Generate ${key} from ${generatedImages[key]}." |
---|
| | } |
---|
| | // Every PDF generation will have a different hash, so only run the |
---|
| | // task if the input is newer than the output. |
---|
| | tasks.getByPath("images.${key}").onlyIf { src.getAsFile().lastModified() > dst.getAsFile().lastModified() } |
---|
| | } |
---|
| | |
---|
| | // Debugging information. |
---|
| | tasks.register("debug") { |
---|
| | description = "Print debugging information." |
---|
| | doLast { |
---|
| | println("project name = <${project.name}>") |
---|
| | println("project directory = <${layout.projectDirectory}>") |
---|
| | // println("project.properties = <${project.properties}>") |
---|
| | println("docTargets = <${docTargets}>") |
---|
| | println("numberedTargets = <${numberedTargets}>") |
---|
| | println("lectureNum = <${lectureNum}>") |
---|
| | println("imageDir = <${imageDir}>") |
---|
| | println("docNames = <${docNames}>") |
---|
| | println("numberedNames = <${numberedNames}>") |
---|
| | println("slidesImages = <${slidesImages}>") |
---|
| | println("slidesFiles = <${slidesFiles}>") |
---|
| | println("examplesImages = <${examplesImages}>") |
---|
| | println("examplesFiles = <${examplesFiles}>") |
---|
| | println("docDependencies = <${docDependencies}>") |
---|
| | println("generatedImages = <${generatedImages}>") |
---|
| | println("latexFlags = <${latexFlags}>") |
---|
| | println("latex.terminalEmulator = <${project.latex.terminalEmulator.get()}>") |
---|
| | println("latex.waitTime = <${project.latex.waitTime.get()}>") |
---|
| | println("latex.waitUnit = <${project.latex.waitUnit.get()}>") |
---|
| | println("latex.pdfLatexCommand = <${project.latex.pdfLatexCommand.get()}>") |
---|
| | println("latex.bibTexCommand = <${project.latex.bibTexCommand.get()}>") |
---|
| | println("latex.inkscapeCommand = <${project.latex.inkscapeCommand.get()}>") |
---|
| | println("latex.gitLatexdiffCommand = <${project.latex.gitLatexdiffCommand.get()}>") |
---|
| | // tasks.matching { it.name.startsWith("pdfLatex") }.forEach { |
---|
| | // println("${it}, ${it.property("artifact")}") |
---|
| | // } |
---|
| | // tasks.forEach { |
---|
| | // println(it) |
---|
| | // println("inputs: ${it.getInputs().getFiles().getFiles()}") |
---|
| | // println("outputs: ${it.getOutputs().getFiles().getFiles()}") |
---|
| | // } |
---|
| | } |
---|
| | } |
---|
| | |
---|