├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── OWNERS ├── README.md ├── build.gradle ├── doc └── gradle-haskell-plugin-drawing1.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src ├── main ├── java │ └── com │ │ └── prezi │ │ └── haskell │ │ └── gradle │ │ └── incubating │ │ ├── AbstractBuildableModelElement.java │ │ ├── AbstractLanguageSourceSet.java │ │ ├── Binary.java │ │ ├── BinaryContainer.java │ │ ├── BinaryInternal.java │ │ ├── BinaryNamingScheme.java │ │ ├── BuildableModelElement.java │ │ ├── DefaultBinaryContainer.java │ │ ├── DefaultFunctionalSourceSet.java │ │ ├── DefaultProjectSourceSet.java │ │ ├── DefaultResourceSet.java │ │ ├── FunctionalSourceSet.java │ │ ├── LanguageSourceSet.java │ │ ├── LanguageSourceSetInternal.java │ │ ├── ProjectSourceSet.java │ │ ├── ResourceSet.java │ │ ├── SourceSetNotationParser.java │ │ ├── package-info.java │ │ └── typeconversion │ │ ├── CharSequenceNotationConverter.java │ │ ├── CharSequenceNotationParser.java │ │ ├── ClosureToSpecNotationParser.java │ │ ├── CompositeNotationConverter.java │ │ ├── CompositeNotationParser.java │ │ ├── EnumFromCharSequenceNotationParser.java │ │ ├── ErrorHandlingNotationParser.java │ │ ├── FlatteningNotationParser.java │ │ ├── JustReturningParser.java │ │ ├── MapKey.java │ │ ├── MapNotationParser.java │ │ ├── NormalizedTimeUnit.java │ │ ├── NotationConvertResult.java │ │ ├── NotationConverter.java │ │ ├── NotationConverterToNotationParserAdapter.java │ │ ├── NotationParser.java │ │ ├── NotationParserBuilder.java │ │ ├── TimeUnitsParser.java │ │ ├── TypeConversionException.java │ │ ├── TypeFilteringNotationConverter.java │ │ ├── TypeInfo.java │ │ ├── TypedNotationParser.java │ │ ├── UnsupportedNotationException.java │ │ └── ValueAwareNotationParser.java ├── resources │ ├── META-INF │ │ └── gradle-plugins │ │ │ └── haskell.properties │ └── com │ │ └── prezi │ │ └── haskell │ │ └── gradle │ │ └── tasks │ │ └── SandFix.hs └── scala │ └── com │ └── prezi │ └── haskell │ └── gradle │ ├── ApiHelper.scala │ ├── HaskellPlugin.scala │ ├── Names.scala │ ├── Profiling.scala │ ├── extension │ ├── HaskellCompilationSupport.scala │ ├── HaskellExtension.scala │ ├── HaskellProject.scala │ ├── ProjectExtender.scala │ ├── SandboxSupport.scala │ ├── StackSupport.scala │ ├── ZippedSandboxArtifactSupport.scala │ └── impl │ │ ├── HaskellCompilationSupportImpl.scala │ │ ├── HaskellProjectImpl.scala │ │ ├── SandboxSupportImpl.scala │ │ ├── StackSupportImpl.scala │ │ └── ZippedSandboxArtifactSupportImpl.scala │ ├── external │ ├── Git.scala │ ├── HaskellTools.scala │ ├── SandFix.scala │ ├── SnapshotVersions.scala │ └── ToolsBase.scala │ ├── io │ └── packers │ │ ├── GradleZipPacker.scala │ │ └── Unpacker.scala │ ├── model │ ├── GHCVersion.scala │ ├── HaskellSourceSet.scala │ ├── Sandbox.scala │ ├── SandboxArtifact.scala │ ├── StackOutputHash.scala │ ├── StackYamlWriter.scala │ └── sandboxstore │ │ ├── ProjectSandboxStore.scala │ │ ├── SandBoxStoreResult.scala │ │ └── SandboxStore.scala │ ├── tasks │ ├── CompileTask.scala │ ├── ConfigureSandboxTasks.scala │ ├── CopySandFix.scala │ ├── DependsOnStoreDependentSandboxes.scala │ ├── GenerateStackYaml.scala │ ├── HaskellDependencies.scala │ ├── HaskellProjectSupport.scala │ ├── REPLTask.scala │ ├── SandboxDirectories.scala │ ├── SandboxInfo.scala │ ├── SandboxPackages.scala │ ├── SandboxTask.scala │ ├── StackExecTask.scala │ ├── StackPathTask.scala │ ├── StackUpdateTask.scala │ ├── StoreDependentSandboxes.scala │ ├── TaskLogging.scala │ ├── TestTask.scala │ ├── UsesSandbox.scala │ ├── UsingGit.scala │ ├── UsingHaskellTools.scala │ └── ZippedSandbox.scala │ └── util │ └── FileLock.scala ├── systest └── scala │ └── com │ └── prezi │ └── haskell │ └── gradle │ └── systests │ ├── BuildingInPrideSpecs.scala │ ├── BuildingTestProjectSpecs.scala │ ├── ChangingCabalFileSpecs.scala │ ├── StreamToStdout.scala │ ├── UsingPride.scala │ └── UsingTestProjects.scala ├── test-projects ├── basic │ ├── build.gradle │ ├── src │ │ └── main │ │ │ └── haskell │ │ │ └── Lib1.hs │ └── test.cabal ├── test1-pride │ ├── app │ │ ├── Setup.hs │ │ ├── app.cabal │ │ ├── build.gradle │ │ └── src │ │ │ ├── main │ │ │ └── haskell │ │ │ │ └── Main.hs │ │ │ └── test │ │ │ └── haskell │ │ │ └── Test.hs │ ├── lib1 │ │ ├── Setup.hs │ │ ├── build.gradle │ │ ├── lib1.cabal │ │ └── src │ │ │ └── main │ │ │ └── haskell │ │ │ └── Lib1.hs │ └── lib2 │ │ ├── Setup.hs │ │ ├── build.gradle │ │ ├── lib2.cabal │ │ └── src │ │ └── main │ │ └── haskell │ │ └── Lib2.hs ├── test1-with-text │ ├── app │ │ ├── Setup.hs │ │ ├── app.cabal │ │ ├── build.gradle │ │ └── src │ │ │ ├── main │ │ │ └── haskell │ │ │ │ └── Main.hs │ │ │ └── test │ │ │ └── haskell │ │ │ └── Test.hs │ ├── build.gradle │ ├── lib1 │ │ ├── Setup.hs │ │ ├── build.gradle │ │ ├── lib1.cabal │ │ └── src │ │ │ └── main │ │ │ └── haskell │ │ │ └── Lib1.hs │ ├── lib2 │ │ ├── Setup.hs │ │ ├── build.gradle │ │ ├── lib2.cabal │ │ └── src │ │ │ └── main │ │ │ └── haskell │ │ │ └── Lib2.hs │ └── settings.gradle ├── test1 │ ├── app │ │ ├── Setup.hs │ │ ├── app.cabal │ │ ├── build.gradle │ │ └── src │ │ │ ├── main │ │ │ └── haskell │ │ │ │ └── Main.hs │ │ │ └── test │ │ │ └── haskell │ │ │ └── Test.hs │ ├── build.gradle │ ├── lib1 │ │ ├── Setup.hs │ │ ├── build.gradle │ │ ├── lib1.cabal │ │ └── src │ │ │ └── main │ │ │ └── haskell │ │ │ └── Lib1.hs │ ├── lib2 │ │ ├── Setup.hs │ │ ├── build.gradle │ │ ├── lib2.cabal │ │ └── src │ │ │ └── main │ │ │ └── haskell │ │ │ └── Lib2.hs │ └── settings.gradle └── test2 │ ├── app │ ├── Setup.hs │ ├── app.cabal │ ├── build.gradle │ └── src │ │ ├── main │ │ └── haskell │ │ │ └── Main.hs │ │ └── test │ │ └── haskell │ │ └── Test.hs │ ├── build.gradle │ ├── lib1 │ ├── Setup.hs │ ├── build.gradle │ ├── lib1.cabal │ └── src │ │ └── main │ │ └── haskell │ │ └── Lib1.hs │ ├── lib2 │ ├── Setup.hs │ ├── build.gradle │ ├── lib2.cabal │ └── src │ │ └── main │ │ └── haskell │ │ └── Lib2.hs │ └── settings.gradle └── test └── scala └── com └── prezi └── haskell └── gradle └── unittests └── ApiHelperSpecs.scala /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | .idea 4 | target/ 5 | classes/ 6 | 7 | # Ignore Gradle GUI config 8 | gradle-app.setting 9 | out 10 | target 11 | *.iml 12 | *.ipr 13 | *.iws 14 | *.orig 15 | .stack-work 16 | stack.yaml 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "sandfix"] 2 | path = sandfix 3 | url = git://github.com/exfalso/sandfix.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | - oraclejdk7 5 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | lambda@prezi.com 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gradle-haskell-plugin 2 | [![Build Status](https://travis-ci.org/prezi/gradle-haskell-plugin.svg)](https://travis-ci.org/prezi/gradle-haskell-plugin) 3 | 4 | Haskell plugin for Gradle 5 | 6 | ## Usage 7 | 8 | Put your _source code_ to `src/main/haskell`, the _test code_ to `src/test/haskell` and the _cabal file_ to the project's root. 9 | Don't forget to set the source directory in the _cabal file_ too: 10 | ``` 11 | hs-source-dirs: src/main/haskell 12 | ``` 13 | 14 | For the default operation all you have to do is _apply the plugin_: 15 | 16 | ```groovy 17 | buildscript { 18 | repositories { 19 | mavenCentral() 20 | } 21 | 22 | dependencies { 23 | classpath 'com.prezi.haskell:gradle-haskell-plugin:0.4+' 24 | } 25 | } 26 | 27 | apply plugin: 'haskell' 28 | ``` 29 | 30 | ## Dependencies, artifacts 31 | Haskell projects creates **zipped sandboxes** as artifacts, and handles dependencies by *chaining* the dependent sandboxes for each GHC/cabal/stack command. 32 | All the dependencies put into the configuration called `main` must be haskell dependencies. 33 | 34 | The following example shows both an _external dependency_ and a _project dependency_, both pointing to an artifact produced by the this plugin. 35 | 36 | ```groovy 37 | dependencies { 38 | main group: "something", name: "lib1", version: "1.+", configuration: 'main' 39 | main project(path: ":lib2", configuration: "main") 40 | } 41 | 42 | ``` 43 | 44 | ## Additional options 45 | It is possible to change the _source set_ used to determine the up-to-date status of the compilation task. 46 | See the following example: 47 | 48 | ```groovy 49 | haskell { 50 | sources { 51 | main { 52 | haskell { 53 | source.srcDir "TODO" 54 | } 55 | } 56 | } 57 | } 58 | ``` 59 | 60 | **NOTE** This only affects _gradle_'s up-to-date checks. You still have to add the source directories to your _cabal file_ too. 61 | 62 | ## Stack support 63 | The plugin now only works through [stack](http://haskellstack.com). 64 | 65 | In this case all you need is a working `stack` executable, everything else is handled by the plugin and *stack*. 66 | 67 | There are a few additional options available in *stack mode*: 68 | 69 | ### Compiler and snapshot versions 70 | To change the GHC version or the *stackage snapshot* to be used, use: 71 | 72 | ```groovy 73 | haskell { 74 | ghcVersion = "ghc-7.10.2" 75 | snapshotId = "lts-3.19" 76 | } 77 | ``` 78 | 79 | ### Package flags 80 | It is possible to customize the *cabal flags* of dependencies installed by *stack*, with the following syntax: 81 | 82 | ```groovy 83 | haskell { 84 | packageFlags["text"] = ["integer-simple": "false"] 85 | } 86 | ``` 87 | 88 | ### Profiling 89 | _Profiling_ is disabled by default. To turn it on, change the `profiling` property of the `haskell` extension: 90 | 91 | ```groovy 92 | haskell { 93 | profiling = true 94 | } 95 | ``` 96 | 97 | or use the `-Pghc-enable-profiling` command line argument. 98 | 99 | ### GHC and cabal location 100 | 101 | It is possible to specify a _cabal config file_ which can point to a non-default global package database, etc: 102 | ```groovy 103 | haskell { 104 | cabalConfigFile = "~/custom/cabal.cfg" 105 | } 106 | ``` 107 | 108 | or `-Pcabal-config-file=~/custom/cabal.cfg`. 109 | 110 | To manage which `cabal`, `ghc` etc. is executed by the plugin, you can override the `PATH` and other environment variables with the following syntax: 111 | 112 | ```groovy 113 | haskell { 114 | envConfigurer { Map envMap -> 115 | def path = envMap.get("PATH") 116 | 117 | envMap.put("PATH", [ 118 | "/custom-ghc-path/bin", 119 | "/custom-cabal-path/bin", 120 | path 121 | ].join(":")) 122 | } 123 | } 124 | ``` 125 | 126 | ## Explanation (stack mode) 127 | The stack mode uses the `extra-package-dbs` option of *stack* which was introduced to support this plugin. The idea is that gradle generates the `stack.yaml` 128 | based on the existing `.cabal` file and the gradle project, and this way it can set up the stack project to use the dependent gradle projects as 129 | binary artifacts. 130 | 131 | The generated *stack* projects have the following properties: 132 | - They use a compiler-only resolver (for example `ghc-7.10.2`) 133 | - All other dependencies are listed in the `extra-deps` section 134 | - The gradle-level dependencies are listed in as `extra-package-dbs` 135 | 136 | This way the *stack* project's *local package database* can be archived as a binary artifact. 137 | 138 | To not loose the stackage *snapshots*, the plugin also uses another tool called [snapshot-versions](https://github.com/vigoo/snapshot-versions), which 139 | generates the `extra-deps` section of the *stack project* by enumerating all the dependencies from the `.cabal` file and reading the snapshot version number 140 | from the configured stackage snapshot. 141 | 142 | To use a package that is **not** part of the configured *stackage snapshot*, but otherwise available from *hackage*, you have to specify it's exact version number 143 | in the `.cabal file`, like: 144 | 145 | ``` 146 | Crypto ==4.2.5.1 147 | ``` 148 | 149 | For the dependencies that are *part* of the snapshot, the `.cabal` file should not put any constraints on. 150 | 151 | ## Details 152 | Applying the plugin adds the following to the project: 153 | 154 | ### Fields 155 | 156 | - `ghcSandbox` is the project's internal _sandbox model_, used by the plugin 157 | - `ghcSandboxRoot` is the root path of the project's _sandbox_ 158 | - `ghcPackageDb` is the _package database_ inside the project's _sandbox_ 159 | - `ghcPrefix` is the directory where _cabal_ installs the project in its _sandbox_ 160 | - `haskellTools` is a helper object used internally by the plugin 161 | 162 | ### Configurations 163 | 164 | - `main` is used to set up the sandbox chain from the dependencies and it also defines the project's sandbox as its _artifact_ 165 | - `test` extends `main` but has no special role currently (because _cabal_ configures the project with its tests in one phase) 166 | 167 | ### Tasks 168 | 169 | - `sandboxInfo` prints some basic information about the project's sandbox 170 | - `sandboxPackages` prints the package list of the project's and its dependent sandboxes 171 | - `compileMain` and `compileTest` all refers to the same task, compiling the whole cabal project 172 | - `assemble` assembles all the files of the project; by default this means running `compileMain` and generating the _ghc-mod cradle_ 173 | - `clean` deletes the `build` and `dist` directories 174 | - `test` compiles the cabal package with tests enabled, and runs them 175 | - `check` executes `test`. It is an extension point to execute other kind of tests. 176 | - `build` executes `assemble` and `check` 177 | - `zipSandbox` packs the whole _sandbox_ as the _artifact_ of the `main` configuration of the project 178 | - `freeze` executes `cabal freeze` 179 | - `repl` executes `cabal repl` 180 | 181 | Additional tasks supporting the ones above: 182 | - `sandboxDirectories` creates the sandbox directory structure 183 | - `sandbox` initializes the package database in the project's sandbox 184 | - `copySandFix` extracts the bunled [_SandFix_](https://github.com/exFalso/sandfix) from the plugin 185 | - `configureSandboxes` sets up `extractDependentSandboxes` and `fixDependentSandboxes` tasks after the dependency resolution is done 186 | - `extractDependentSandboxes` extracts the resolved artifacts to temporary directories inside `build/deps` 187 | - `fixDependentSandboxes` clones the extracted sandboxes and runs _SandFix_ on them 188 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url "http://dl.bintray.com/releashaus/release" } 4 | maven { url "https://oss.sonatype.org/content/groups/public" } 5 | } 6 | 7 | dependencies { 8 | classpath 'org.scoverage:gradle-scoverage:1.0.8' 9 | } 10 | } 11 | 12 | repositories { 13 | mavenLocal() 14 | mavenCentral() 15 | 16 | maven { 17 | url "http://dl.bintray.com/scalaz/releases" 18 | } 19 | } 20 | 21 | if (hasProperty("release")) { 22 | version = [ "git", "describe", "--match", "[0-9]*", "--dirty"].execute().text.trim() 23 | } else { 24 | version = [ "git", "describe", "--match", "[0-9]*", "--abbrev=0"].execute().text.trim() + "-SNAPSHOT" 25 | } 26 | 27 | task version << { 28 | println "Version: ${version}" 29 | } 30 | 31 | apply plugin: 'scala' 32 | apply plugin: 'idea' 33 | apply plugin: 'maven' 34 | apply plugin: org.scoverage.ScoveragePlugin 35 | 36 | sourceCompatibility = "1.6" 37 | targetCompatibility = "1.6" 38 | def scalaVersion = "2.11.8" 39 | def ossRelease = hasProperty("oss") 40 | def signArtifacts = ossRelease || hasProperty("sign") 41 | 42 | group = 'com.prezi.haskell' 43 | description = 'Gradle plugin for Haskell projects' 44 | 45 | dependencies { 46 | compile gradleApi() 47 | compile group: "org.scala-lang", name: "scala-library", version: "$scalaVersion" 48 | compile group: "org.scala-lang", name: "scala-compiler", version: "$scalaVersion" 49 | compile group: "com.jsuereth", name: "scala-arm_2.11", version: "2.0" 50 | compile group: "commons-io", name: "commons-io", version: "2.4" 51 | compile group: 'commons-codec', name: 'commons-codec', version: '1.10' 52 | 53 | // Required only for the incubating stuff brought over from Gradle 2.0 54 | compile "commons-lang:commons-lang:2.6" 55 | 56 | testCompile group: "org.specs2", name: "specs2-core_2.11", version: "3.6.5" 57 | testCompile group: "org.specs2", name: "specs2-mock_2.11", version: "3.6.5" 58 | testCompile group: "org.specs2", name: "specs2-junit_2.11", version: "3.6.5" 59 | 60 | scoverage 'org.scoverage:scalac-scoverage-plugin_2.11:1.0.4' 61 | scoverage 'org.scoverage:scalac-scoverage-runtime_2.11:1.0.4' 62 | } 63 | 64 | tasks.withType(ScalaCompile) { 65 | scalaCompileOptions.useAnt = false 66 | scalaCompileOptions.additionalParameters = ["-unchecked", "-deprecation", "-feature", "-Xfatal-warnings"] 67 | } 68 | 69 | checkScoverage { 70 | // minimumLineRate = 0.9 71 | } 72 | 73 | uploadArchives { 74 | repositories { 75 | repositories { 76 | mavenDeployer { 77 | pom.project { 78 | url "http://github.com/prezi/gradle-haskell-plugin" 79 | name project.name 80 | description project.description 81 | packaging 'jar' 82 | 83 | scm { 84 | url "http://github.com/prezi/gradle-haskell-plugin" 85 | connection "scm:git@github.com:prezi/gradle-haskell-plugin.git" 86 | developerConnection "scm:git@github.com:prezi/gradle-haskell-plugin.git" 87 | } 88 | 89 | licenses { 90 | license { 91 | name "The Apache Software License, Version 2.0" 92 | url "http://www.apache.org/licenses/LICENSE-2.0.txt" 93 | distribution "repo" 94 | } 95 | } 96 | 97 | developers { 98 | developer { 99 | id "vigoo" 100 | name "Daniel Vigovszky" 101 | email 'daniel.vigovszky@gmail.com' 102 | } 103 | } 104 | } 105 | 106 | if (ossRelease) { 107 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { 108 | authentication(userName: ossrhUsername, password: ossrhPassword) 109 | } 110 | 111 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { 112 | authentication(userName: ossrhUsername, password: ossrhPassword) 113 | } 114 | } 115 | else if (project.hasProperty("nexusUser") && project.hasProperty("nexusPassword")) { 116 | def user = project.getProperty("nexusUser") 117 | def password = project.getProperty("nexusPassword") 118 | 119 | repository(url: "https://artifactory.prezi.com/plugins-release-local/") { 120 | authentication(userName: user, password: password) 121 | } 122 | snapshotRepository(url: "https://artifactory.prezi.com/plugins-snapshot-local/") { 123 | authentication(userName: user, password: password) 124 | } 125 | } 126 | } 127 | } 128 | } 129 | } 130 | 131 | if (signArtifacts) { 132 | apply plugin: "signing" 133 | 134 | signing { 135 | sign configurations.archives 136 | } 137 | 138 | uploadArchives { 139 | repositories { 140 | mavenDeployer { 141 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 142 | } 143 | } 144 | } 145 | } 146 | 147 | task javadocJar(type: Jar) { 148 | dependsOn scaladoc 149 | classifier = "javadoc" 150 | from "build/docs/scaladoc" 151 | } 152 | 153 | task sourcesJar(type: Jar) { 154 | from sourceSets.main.allSource 155 | classifier = "sources" 156 | } 157 | 158 | jar { 159 | from rootProject.file("LICENSE") 160 | } 161 | 162 | artifacts { 163 | archives jar 164 | archives javadocJar 165 | archives sourcesJar 166 | } 167 | 168 | sourceSets { 169 | systest { 170 | scala.srcDir file('src/systest/scala') 171 | resources.srcDir file('src/systest/resources') 172 | compileClasspath = sourceSets.main.output + configurations.testCompile 173 | runtimeClasspath = output + compileClasspath 174 | } 175 | } 176 | 177 | task integTestBundle { 178 | dependsOn jar 179 | 180 | doLast { 181 | def source_resources = 'src/main/resources' 182 | 183 | def destination = 'build/integ-test-bundle' 184 | delete destination 185 | new File(destination).mkdirs() 186 | 187 | def lib = new File(destination, "lib") 188 | lib.mkdirs() 189 | copy { 190 | into lib 191 | from configurations.runtime 192 | } 193 | copy { 194 | into lib 195 | from 'build/libs' 196 | include "gradle-haskell-plugin-${version}.jar" 197 | } 198 | } 199 | } 200 | 201 | test { 202 | testLogging.showStandardStreams = true 203 | systemProperty "specs2.commandline", "console" 204 | } 205 | 206 | task systest(type: Test) { 207 | dependsOn integTestBundle 208 | include "**/systests/**" 209 | testClassesDir = sourceSets.systest.output.classesDir 210 | classpath += sourceSets.systest.runtimeClasspath 211 | 212 | testLogging.showStandardStreams = true 213 | systemProperty "specs2.commandline", "console" 214 | systemProperty "test-projects-dir", "$projectDir/src/test-projects" 215 | systemProperty "plugin-build-dir", "$buildDir" 216 | 217 | reports { 218 | html { 219 | destination = "$buildDir/reports/systests" 220 | } 221 | 222 | junitXml { 223 | destination = "$buildDir/reports/systests" 224 | } 225 | } 226 | } 227 | 228 | task wrapper(type: Wrapper) { 229 | gradleVersion = '2.4' 230 | } 231 | 232 | // systest requires working haskell/stack on CI nodes, so its disabled for now 233 | // check.dependsOn systest -------------------------------------------------------------------------------- /doc/gradle-haskell-plugin-drawing1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prezi/gradle-haskell-plugin/a1332e88b9d7aa46173b790fa48beb9a5c3a2cf6/doc/gradle-haskell-plugin-drawing1.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prezi/gradle-haskell-plugin/a1332e88b9d7aa46173b790fa48beb9a5c3a2cf6/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon May 02 13:59:43 CEST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/AbstractBuildableModelElement.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.Task; 4 | import org.gradle.api.internal.tasks.DefaultTaskDependency; 5 | import org.gradle.api.tasks.TaskDependency; 6 | 7 | import java.util.Collections; 8 | import java.util.Set; 9 | 10 | public class AbstractBuildableModelElement implements BuildableModelElement { 11 | private final DefaultTaskDependency buildDependencies = new DefaultTaskDependency(); 12 | private Task lifecycleTask; 13 | 14 | public Task getBuildTask() { 15 | return lifecycleTask; 16 | } 17 | 18 | public void setBuildTask(Task lifecycleTask) { 19 | this.lifecycleTask = lifecycleTask; 20 | lifecycleTask.dependsOn(buildDependencies); 21 | } 22 | 23 | public TaskDependency getBuildDependencies() { 24 | return new TaskDependency() { 25 | public Set getDependencies(Task other) { 26 | if (lifecycleTask == null) { 27 | return buildDependencies.getDependencies(other); 28 | } 29 | return Collections.singleton(lifecycleTask); 30 | } 31 | }; 32 | } 33 | 34 | public void builtBy(Object... tasks) { 35 | buildDependencies.add(tasks); 36 | } 37 | 38 | public boolean hasBuildDependencies() { 39 | return buildDependencies.getDependencies(lifecycleTask).size() > 0; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/AbstractLanguageSourceSet.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.apache.commons.lang.StringUtils; 4 | import org.gradle.api.Action; 5 | import org.gradle.api.Task; 6 | import org.gradle.api.file.SourceDirectorySet; 7 | 8 | public abstract class AbstractLanguageSourceSet extends AbstractBuildableModelElement implements LanguageSourceSetInternal { 9 | private final String name; 10 | private final String fullName; 11 | private final String displayName; 12 | private final SourceDirectorySet source; 13 | private boolean generated; 14 | private Task generatorTask; 15 | 16 | public AbstractLanguageSourceSet(String name, FunctionalSourceSet parent, String typeName, SourceDirectorySet source) { 17 | this.name = name; 18 | this.fullName = parent.getName() + StringUtils.capitalize(name); 19 | this.displayName = String.format("%s '%s:%s'", typeName, parent.getName(), name); 20 | this.source = source; 21 | super.builtBy(source.getBuildDependencies()); 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public String getFullName() { 29 | return fullName; 30 | } 31 | 32 | @Override 33 | public void builtBy(Object... tasks) { 34 | generated = true; 35 | super.builtBy(tasks); 36 | } 37 | 38 | public void generatedBy(Task generatorTask) { 39 | this.generatorTask = generatorTask; 40 | } 41 | 42 | public Task getGeneratorTask() { 43 | return generatorTask; 44 | } 45 | 46 | public boolean getMayHaveSources() { 47 | // TODO:DAZ This doesn't take into account build dependencies of the SourceDirectorySet. 48 | // Should just ditch SourceDirectorySet from here since it's not really a great model, and drags in too much baggage. 49 | return generated || !source.isEmpty(); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return displayName; 55 | } 56 | 57 | public void source(Action config) { 58 | config.execute(getSource()); 59 | } 60 | 61 | public SourceDirectorySet getSource() { 62 | return source; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/Binary.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.Incubating; 4 | import org.gradle.api.Named; 5 | import org.gradle.internal.HasInternalProtocol; 6 | 7 | /** 8 | * A physical binary artifact, which can run on a particular platform or runtime. 9 | */ 10 | @Incubating 11 | @HasInternalProtocol 12 | public interface Binary extends BuildableModelElement, Named { 13 | /** 14 | * Returns a human-consumable display name for this binary. 15 | */ 16 | String getDisplayName(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/BinaryContainer.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer; 4 | import org.gradle.api.Incubating; 5 | 6 | /** 7 | * A container for project binaries, which represent physical artifacts that can run on a particular platform or runtime. 8 | * Added to a project by the {@link org.gradle.language.base.plugins.LanguageBasePlugin}. 9 | */ 10 | @Incubating 11 | public interface BinaryContainer extends ExtensiblePolymorphicDomainObjectContainer {} 12 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/BinaryInternal.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | public interface BinaryInternal extends Binary { 4 | BinaryNamingScheme getNamingScheme(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/BinaryNamingScheme.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.Nullable; 4 | 5 | import java.util.List; 6 | 7 | public interface BinaryNamingScheme { 8 | String getLifecycleTaskName(); 9 | 10 | String getTaskName(@Nullable String verb); 11 | 12 | String getTaskName(@Nullable String verb, @Nullable String target); 13 | 14 | String getOutputDirectoryBase(); 15 | 16 | String getDescription(); 17 | 18 | List getVariantDimensions(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/BuildableModelElement.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.Buildable; 4 | import org.gradle.api.Incubating; 5 | import org.gradle.api.Nullable; 6 | import org.gradle.api.Task; 7 | 8 | /** 9 | * A model element that is directly buildable. 10 | * Such an element mirrors a specified lifecycle task in the DAG, and can accept dependencies which are then associated with the lifecycle task. 11 | */ 12 | @Incubating 13 | public interface BuildableModelElement extends Buildable { 14 | /** 15 | * Returns the 'lifecycle' task associated with the construction of this element. 16 | */ 17 | @Nullable 18 | Task getBuildTask(); 19 | 20 | /** 21 | * Associates a 'lifecycle' task with the construction of this element. 22 | */ 23 | void setBuildTask(Task lifecycleTask); 24 | 25 | /** 26 | * Adds a task that is required for the construction of this element. 27 | * A task added this way is then added as a dependency of the associated lifecycle task. 28 | */ 29 | void builtBy(Object... tasks); 30 | 31 | boolean hasBuildDependencies(); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/DefaultBinaryContainer.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer; 4 | import org.gradle.internal.reflect.Instantiator; 5 | 6 | public class DefaultBinaryContainer extends DefaultPolymorphicDomainObjectContainer implements BinaryContainer { 7 | public DefaultBinaryContainer(Instantiator instantiator) { 8 | super(Binary.class, instantiator); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/DefaultFunctionalSourceSet.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer; 4 | import org.gradle.internal.reflect.Instantiator; 5 | 6 | public class DefaultFunctionalSourceSet extends DefaultPolymorphicDomainObjectContainer implements FunctionalSourceSet { 7 | private final String name; 8 | 9 | public DefaultFunctionalSourceSet(String name, Instantiator instantiator) { 10 | super(LanguageSourceSet.class, instantiator); 11 | this.name = name; 12 | } 13 | 14 | @Override 15 | public String toString() { 16 | return String.format("source set '%s'", name); 17 | } 18 | 19 | public String getName() { 20 | return name; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/DefaultProjectSourceSet.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.internal.AbstractNamedDomainObjectContainer; 4 | import org.gradle.internal.reflect.Instantiator; 5 | 6 | public class DefaultProjectSourceSet extends AbstractNamedDomainObjectContainer implements ProjectSourceSet { 7 | public DefaultProjectSourceSet(Instantiator instantiator) { 8 | super(FunctionalSourceSet.class, instantiator); 9 | } 10 | 11 | @Override 12 | protected FunctionalSourceSet doCreate(String name) { 13 | return getInstantiator().newInstance(DefaultFunctionalSourceSet.class, name, getInstantiator()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/DefaultResourceSet.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.file.SourceDirectorySet; 4 | 5 | public class DefaultResourceSet extends AbstractLanguageSourceSet implements ResourceSet { 6 | 7 | public DefaultResourceSet(String name, SourceDirectorySet source, FunctionalSourceSet parent) { 8 | super(name, parent, "resources", source); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/FunctionalSourceSet.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer; 4 | import org.gradle.api.Incubating; 5 | import org.gradle.api.Named; 6 | 7 | /** 8 | * A container holding {@link LanguageSourceSet}s with a similar function 9 | * (production code, test code, etc.). 10 | */ 11 | @Incubating 12 | public interface FunctionalSourceSet extends ExtensiblePolymorphicDomainObjectContainer, Named {} 13 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/LanguageSourceSet.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.Action; 4 | import org.gradle.api.Incubating; 5 | import org.gradle.api.Named; 6 | import org.gradle.api.Task; 7 | import org.gradle.api.file.SourceDirectorySet; 8 | import org.gradle.internal.HasInternalProtocol; 9 | 10 | /** 11 | * A set of sources for a programming language. 12 | */ 13 | @Incubating 14 | @HasInternalProtocol 15 | public interface LanguageSourceSet extends Named, BuildableModelElement { 16 | // TODO: do we want to keep using SourceDirectorySet in the new API? 17 | // would feel more natural if dirs could be added directly to LanguageSourceSet 18 | // could also think about extending SourceDirectorySet 19 | 20 | /** 21 | * The source files. 22 | */ 23 | SourceDirectorySet getSource(); 24 | 25 | /** 26 | * Configure the sources 27 | */ 28 | void source(Action config); 29 | 30 | void generatedBy(Task generatorTask); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/LanguageSourceSetInternal.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.Task; 4 | 5 | public interface LanguageSourceSetInternal extends LanguageSourceSet { 6 | 7 | /** 8 | * A unique name for this source set across all functional source sets. 9 | */ 10 | String getFullName(); 11 | 12 | /** 13 | * Return true if the source set contains sources, or if the source set is generated. 14 | */ 15 | boolean getMayHaveSources(); 16 | 17 | Task getGeneratorTask(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/ProjectSourceSet.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.Incubating; 4 | import org.gradle.api.NamedDomainObjectContainer; 5 | 6 | /** 7 | * A container of {@link FunctionalSourceSet}s. Added to a project by the 8 | * {@link org.gradle.language.base.plugins.LanguageBasePlugin}. 9 | */ 10 | @Incubating 11 | public interface ProjectSourceSet extends NamedDomainObjectContainer {} 12 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/ResourceSet.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import org.gradle.api.Incubating; 4 | 5 | /** 6 | * A set of resource files. 7 | */ 8 | @Incubating 9 | public interface ResourceSet extends LanguageSourceSet {} 10 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/SourceSetNotationParser.java: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.incubating; 2 | 3 | import com.prezi.haskell.gradle.incubating.typeconversion.NotationParser; 4 | import com.prezi.haskell.gradle.incubating.typeconversion.NotationParserBuilder; 5 | import com.prezi.haskell.gradle.incubating.typeconversion.TypeInfo; 6 | import com.prezi.haskell.gradle.incubating.typeconversion.TypedNotationParser; 7 | 8 | import java.util.Collection; 9 | import java.util.Collections; 10 | import java.util.LinkedHashSet; 11 | import java.util.Set; 12 | 13 | public class SourceSetNotationParser { 14 | public static NotationParser> parser() { 15 | return NotationParserBuilder 16 | .toType(new TypeInfo>(Set.class)) 17 | .parser(new FunctionalSourceSetConverter()) 18 | .parser(new SingleLanguageSourceSetConverter()) 19 | .parser(new LanguageSourceSetCollectionConverter()) 20 | .toComposite(); 21 | } 22 | 23 | private static class FunctionalSourceSetConverter extends TypedNotationParser> { 24 | private FunctionalSourceSetConverter() { 25 | super(FunctionalSourceSet.class); 26 | } 27 | 28 | @Override 29 | protected Set parseType(FunctionalSourceSet notation) { 30 | return notation; 31 | } 32 | } 33 | 34 | private static class SingleLanguageSourceSetConverter extends TypedNotationParser> { 35 | private SingleLanguageSourceSetConverter() { 36 | super(LanguageSourceSet.class); 37 | } 38 | 39 | @Override 40 | protected Set parseType(LanguageSourceSet notation) { 41 | return Collections.singleton(notation); 42 | } 43 | } 44 | 45 | private static class LanguageSourceSetCollectionConverter extends TypedNotationParser, Set> { 46 | private LanguageSourceSetCollectionConverter() { 47 | super(new TypeInfo>(Collection.class)); 48 | } 49 | 50 | @Override 51 | protected Set parseType(Collection notation) { 52 | return new LinkedHashSet(notation); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Classes and interfaces brought over from Gradle 2.0. 3 | * These are here so we don't depend on incubating features. 4 | */ 5 | package com.prezi.haskell.gradle.incubating; 6 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/CharSequenceNotationConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.util.Collection; 20 | 21 | class CharSequenceNotationConverter implements NotationConverter { 22 | private final NotationConverter delegate; 23 | 24 | public CharSequenceNotationConverter(NotationConverter delegate) { 25 | this.delegate = delegate; 26 | } 27 | 28 | public void convert(N notation, NotationConvertResult result) throws TypeConversionException { 29 | if (notation instanceof CharSequence) { 30 | CharSequence charSequence = (CharSequence) notation; 31 | delegate.convert(charSequence.toString(), result); 32 | } 33 | } 34 | 35 | public void describe(Collection candidateFormats) { 36 | delegate.describe(candidateFormats); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/CharSequenceNotationParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.util.Collection; 20 | 21 | class CharSequenceNotationParser implements NotationConverter { 22 | public void convert(String notation, NotationConvertResult result) throws TypeConversionException { 23 | result.converted(notation); 24 | } 25 | 26 | public void describe(Collection candidateFormats) { 27 | candidateFormats.add("String or CharSequence instances."); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/ClosureToSpecNotationParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import groovy.lang.Closure; 20 | import org.gradle.api.specs.Spec; 21 | import org.gradle.api.specs.Specs; 22 | 23 | import java.util.Collection; 24 | 25 | public class ClosureToSpecNotationParser implements NotationConverter> { 26 | private final Class type; 27 | 28 | public ClosureToSpecNotationParser(Class type) { 29 | this.type = type; 30 | } 31 | 32 | public void convert(Closure notation, NotationConvertResult> result) throws TypeConversionException { 33 | Spec spec = Specs.convertClosureToSpec(notation); 34 | result.converted(spec); 35 | } 36 | 37 | public void describe(Collection candidateFormats) { 38 | candidateFormats.add(String.format("Closure that returns boolean and takes a single %s as a parameter.", type.getSimpleName())); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/CompositeNotationConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.util.Collection; 20 | import java.util.List; 21 | 22 | public class CompositeNotationConverter implements NotationConverter { 23 | private final List> converters; 24 | 25 | public CompositeNotationConverter(List> converters) { 26 | this.converters = converters; 27 | } 28 | 29 | public void convert(N notation, NotationConvertResult result) throws TypeConversionException { 30 | for (int i = 0; !result.hasResult() && i < converters.size(); i++) { 31 | NotationConverter converter = converters.get(i); 32 | converter.convert(notation, result); 33 | } 34 | } 35 | 36 | public void describe(Collection candidateFormats) { 37 | for (NotationConverter converter : converters) { 38 | converter.describe(candidateFormats); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/CompositeNotationParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.util.Collection; 20 | 21 | public class CompositeNotationParser implements NotationParser { 22 | private final Collection> delegates; 23 | 24 | public CompositeNotationParser(Collection> delegates) { 25 | assert delegates != null : "delegates cannot be null!"; 26 | this.delegates = delegates; 27 | } 28 | 29 | public void describe(Collection candidateFormats) { 30 | for (NotationParser delegate : delegates) { 31 | delegate.describe(candidateFormats); 32 | } 33 | } 34 | 35 | public T parseNotation(N notation) { 36 | for (NotationParser delegate : delegates) { 37 | try { 38 | return delegate.parseNotation(notation); 39 | } catch (UnsupportedNotationException e) { 40 | // Ignore 41 | } 42 | } 43 | 44 | throw new UnsupportedNotationException(notation); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/EnumFromCharSequenceNotationParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import org.gradle.api.specs.Spec; 20 | import org.gradle.util.CollectionUtils; 21 | import org.gradle.util.GUtil; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Arrays; 25 | import java.util.Collection; 26 | import java.util.List; 27 | 28 | public class EnumFromCharSequenceNotationParser implements ValueAwareNotationParser { 29 | private final Class type; 30 | 31 | public EnumFromCharSequenceNotationParser(Class enumType) { 32 | assert enumType.isEnum() : "resultingType must be enum"; 33 | this.type = enumType; 34 | } 35 | 36 | public T parseNotation(CharSequence notation) throws UnsupportedNotationException, TypeConversionException { 37 | final String enumString = notation.toString(); 38 | List enumConstants = Arrays.asList(type.getEnumConstants()); 39 | T match = CollectionUtils.findFirst(enumConstants, new Spec() { 40 | public boolean isSatisfiedBy(T enumValue) { 41 | return enumValue.name().equalsIgnoreCase(enumString); 42 | } 43 | }); 44 | if (match == null) { 45 | throw new TypeConversionException( 46 | String.format("Cannot coerce string value '%s' to an enum value of type '%s' (valid case insensitive values: %s)", 47 | enumString, type.getName(), CollectionUtils.toStringList(Arrays.asList(type.getEnumConstants())) 48 | ) 49 | ); 50 | } else { 51 | return match; 52 | } 53 | } 54 | 55 | public void describe(Collection candidateFormats) { 56 | List values = new ArrayList(); 57 | describeValues(values); 58 | candidateFormats.add(String.format("One of the following values: %s", GUtil.toString(values))); 59 | } 60 | 61 | public void describeValues(Collection collector) { 62 | final Enum[] enumConstants = type.getEnumConstants(); 63 | for (Enum enumConstant : enumConstants) { 64 | collector.add(enumConstant.name()); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/ErrorHandlingNotationParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collection; 21 | import java.util.List; 22 | 23 | class ErrorHandlingNotationParser implements NotationParser { 24 | private final String targetTypeDisplayName; 25 | private final String invalidNotationMessage; 26 | private final boolean allowNullInput; 27 | private final NotationParser delegate; 28 | 29 | public ErrorHandlingNotationParser(String targetTypeDisplayName, String invalidNotationMessage, boolean allowNullInput, NotationParser delegate) { 30 | this.targetTypeDisplayName = targetTypeDisplayName; 31 | this.invalidNotationMessage = invalidNotationMessage; 32 | this.allowNullInput = allowNullInput; 33 | this.delegate = delegate; 34 | } 35 | 36 | public void describe(Collection candidateFormats) { 37 | delegate.describe(candidateFormats); 38 | } 39 | 40 | public T parseNotation(N notation) { 41 | String failure; 42 | if (notation == null && !allowNullInput) { 43 | failure = String.format("Cannot convert a null value to %s.", targetTypeDisplayName); 44 | } else { 45 | try { 46 | return delegate.parseNotation(notation); 47 | } catch (UnsupportedNotationException e) { 48 | failure = String.format("Cannot convert the provided notation to %s: %s.", targetTypeDisplayName, e.getNotation()); 49 | } 50 | } 51 | 52 | List formats = new ArrayList(); 53 | describe(formats); 54 | 55 | throw new UnsupportedNotationException(notation, failure, invalidNotationMessage, formats); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/FlatteningNotationParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import org.gradle.util.GUtil; 20 | 21 | import java.util.Collection; 22 | import java.util.LinkedHashSet; 23 | import java.util.Set; 24 | 25 | /** 26 | * Flattens or collectionizes input and passes the input notations to the delegates. Returns a set. 27 | */ 28 | public class FlatteningNotationParser implements NotationParser> { 29 | 30 | private final NotationParser delegate; 31 | 32 | public FlatteningNotationParser(NotationParser delegate) { 33 | assert delegate != null : "delegate cannot be null"; 34 | this.delegate = delegate; 35 | } 36 | 37 | public void describe(Collection candidateFormats) { 38 | delegate.describe(candidateFormats); 39 | candidateFormats.add("Collections or arrays of any other supported format. Nested collections/arrays will be flattened."); 40 | } 41 | 42 | public Set parseNotation(Object notation) { 43 | Set out = new LinkedHashSet(); 44 | Collection notations = GUtil.collectionize(notation); 45 | for (Object n : notations) { 46 | out.add(delegate.parseNotation(n)); 47 | } 48 | return out; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/JustReturningParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.prezi.haskell.gradle.incubating.typeconversion; 17 | 18 | import java.util.Collection; 19 | 20 | public class JustReturningParser implements NotationParser { 21 | 22 | private final Class passThroughType; 23 | 24 | public JustReturningParser(Class passThroughType) { 25 | this.passThroughType = passThroughType; 26 | } 27 | 28 | public void describe(Collection candidateFormats) { 29 | candidateFormats.add(String.format("Instances of %s.", passThroughType.getSimpleName())); 30 | } 31 | 32 | public T parseNotation(N notation) { 33 | if (!passThroughType.isInstance(notation)) { 34 | throw new UnsupportedNotationException(notation); 35 | } 36 | return passThroughType.cast(notation); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/MapKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | @Retention(RetentionPolicy.RUNTIME) 25 | @Target(ElementType.PARAMETER) 26 | public @interface MapKey { 27 | String value(); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/MapNotationParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.prezi.haskell.gradle.incubating.typeconversion; 17 | 18 | import org.gradle.api.InvalidUserDataException; 19 | import org.gradle.api.tasks.Optional; 20 | import org.gradle.internal.UncheckedException; 21 | import org.gradle.util.ConfigureUtil; 22 | 23 | import java.lang.annotation.Annotation; 24 | import java.lang.reflect.InvocationTargetException; 25 | import java.lang.reflect.Method; 26 | import java.util.*; 27 | 28 | /** 29 | * Converts a {@code Map} to the target type. Subclasses should define a {@code T parseMap()} method which takes a parameter 30 | * for each key value required from the source map. Each parameter should be annotated with a {@code @MapKey} annotation, and can also 31 | * be annotated with a {@code @optional} annotation. 32 | */ 33 | public abstract class MapNotationParser extends TypedNotationParser { 34 | private final Method convertMethod; 35 | private final String[] keyNames; 36 | private final boolean[] optional; 37 | 38 | public MapNotationParser() { 39 | super(Map.class); 40 | convertMethod = findConvertMethod(); 41 | keyNames = new String[convertMethod.getParameterAnnotations().length]; 42 | optional = new boolean[convertMethod.getParameterAnnotations().length]; 43 | for (int i = 0; i < convertMethod.getParameterAnnotations().length; i++) { 44 | Annotation[] annotations = convertMethod.getParameterAnnotations()[i]; 45 | keyNames[i] = keyName(annotations); 46 | optional[i] = optional(annotations); 47 | } 48 | } 49 | 50 | private Method findConvertMethod() { 51 | for (Method method : getClass().getDeclaredMethods()) { 52 | if (method.getName().equals("parseMap")) { 53 | method.setAccessible(true); 54 | return method; 55 | } 56 | } 57 | throw new UnsupportedOperationException(String.format("No parseMap() method found on class %s.", getClass().getSimpleName())); 58 | } 59 | 60 | public void describe(Collection candidateFormats) { 61 | candidateFormats.add("Maps"); 62 | } 63 | 64 | public T parseType(Map values) throws UnsupportedNotationException { 65 | Map mutableValues = new HashMap(values); 66 | Set missing = new TreeSet(); 67 | 68 | Object[] params = new Object[convertMethod.getParameterTypes().length]; 69 | for (int i = 0; i < params.length; i++) { 70 | String keyName = keyNames[i]; 71 | boolean optional = this.optional[i]; 72 | Class type = convertMethod.getParameterTypes()[i]; 73 | Object value; 74 | if (type.equals(String.class)) { 75 | value = get(mutableValues, keyName); 76 | } else { 77 | value = type.cast(mutableValues.get(keyName)); 78 | } 79 | if (!optional && value == null) { 80 | missing.add(keyName); 81 | } 82 | mutableValues.remove(keyName); 83 | params[i] = value; 84 | } 85 | if (!missing.isEmpty()) { 86 | //below could be better. 87 | //Throwing InvalidUserDataException here means that useful context information (including candidate formats, etc.) is not presented to the user 88 | throw new InvalidUserDataException(String.format("Required keys %s are missing from map %s.", missing, values)); 89 | } 90 | 91 | T result; 92 | try { 93 | result = (T) convertMethod.invoke(this, params); 94 | } catch (IllegalAccessException e) { 95 | throw UncheckedException.throwAsUncheckedException(e); 96 | } catch (InvocationTargetException e) { 97 | throw UncheckedException.unwrapAndRethrow(e); 98 | } 99 | 100 | ConfigureUtil.configureByMap(mutableValues, result); 101 | return result; 102 | } 103 | 104 | private boolean optional(Annotation[] annotations) { 105 | for (Annotation annotation : annotations) { 106 | if (annotation instanceof Optional) { 107 | return true; 108 | } 109 | } 110 | return false; 111 | } 112 | 113 | private String keyName(Annotation[] annotations) { 114 | for (Annotation annotation : annotations) { 115 | if (annotation instanceof MapKey) { 116 | return ((MapKey) annotation).value(); 117 | } 118 | } 119 | throw new UnsupportedOperationException("No @Key annotation on parameter of parseMap() method"); 120 | } 121 | 122 | protected String get(Map args, String key) { 123 | Object value = args.get(key); 124 | String str = value != null ? value.toString() : null; 125 | if (str != null && str.length() == 0) { 126 | return null; 127 | } 128 | return str; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/NormalizedTimeUnit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.util.concurrent.TimeUnit; 20 | 21 | public class NormalizedTimeUnit { 22 | 23 | private int value; 24 | private TimeUnit timeUnit; 25 | 26 | public NormalizedTimeUnit(int value, TimeUnit timeUnit) { 27 | this.value = value; 28 | this.timeUnit = timeUnit; 29 | } 30 | 31 | public static NormalizedTimeUnit millis(int value) { 32 | return new NormalizedTimeUnit(value, TimeUnit.MILLISECONDS); 33 | } 34 | 35 | public int getValue() { 36 | return value; 37 | } 38 | 39 | public TimeUnit getTimeUnit() { 40 | return timeUnit; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/NotationConvertResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | public interface NotationConvertResult { 20 | boolean hasResult(); 21 | 22 | /** 23 | * Invoked when a {@link NotationConverter} is able to convert a notation to a result. 24 | */ 25 | void converted(T result); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/NotationConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.util.Collection; 20 | 21 | /** 22 | * A converter from notations of type {@link N} to results of type {@link T}. 23 | * 24 | *

This interface represents an SPI used to implement notation parsers, not the API to use to perform the conversions. Use {@link NotationParser} instead for this. 25 | */ 26 | public interface NotationConverter { 27 | /** 28 | * Attempt to convert the given notation. 29 | * 30 | * @throws TypeConversionException when the notation is recognized but cannot be converted for some reason. 31 | */ 32 | void convert(N notation, NotationConvertResult result) throws TypeConversionException; 33 | 34 | /** 35 | * Describes the formats that this converter accepts. 36 | */ 37 | void describe(Collection candidateFormats); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/NotationConverterToNotationParserAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.util.Collection; 20 | 21 | class NotationConverterToNotationParserAdapter implements NotationParser { 22 | private final NotationConverter converter; 23 | 24 | public NotationConverterToNotationParserAdapter(NotationConverter converter) { 25 | this.converter = converter; 26 | } 27 | 28 | public T parseNotation(N notation) throws UnsupportedNotationException, TypeConversionException { 29 | ResultImpl result = new ResultImpl(); 30 | converter.convert(notation, result); 31 | if (!result.hasResult) { 32 | throw new UnsupportedNotationException(notation); 33 | } 34 | return result.result; 35 | } 36 | 37 | public void describe(Collection candidateFormats) { 38 | converter.describe(candidateFormats); 39 | } 40 | 41 | private static class ResultImpl implements NotationConvertResult { 42 | private boolean hasResult; 43 | private T result; 44 | 45 | public boolean hasResult() { 46 | return hasResult; 47 | } 48 | 49 | public void converted(T result) { 50 | hasResult = true; 51 | this.result = result; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/NotationParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.util.Collection; 20 | 21 | /** 22 | * A parser from notations of type {@link N} to a result of type {@link T}. This interface is used by clients to perform the parsing. To implement a parser, you should use {@link NotationConverter} 23 | * instead. 24 | */ 25 | public interface NotationParser { 26 | /** 27 | * @throws UnsupportedNotationException When the supplied notation is not handled by this parser. 28 | * @throws TypeConversionException When the supplied notation is recognised by this parser but is badly formed and cannot be converted to the target type. 29 | */ 30 | T parseNotation(N notation) throws UnsupportedNotationException, TypeConversionException; 31 | 32 | /** 33 | * Describes the formats that the parser accepts. 34 | */ 35 | void describe(Collection candidateFormats); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/NotationParserBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.util.Collection; 20 | import java.util.LinkedList; 21 | import java.util.List; 22 | import java.util.Set; 23 | 24 | public class NotationParserBuilder { 25 | private TypeInfo resultingType; 26 | private String invalidNotationMessage; 27 | private String typeDisplayName; 28 | private boolean implicitConverters = true; 29 | private boolean allowNullInput; 30 | private final Collection> notationParsers = new LinkedList>(); 31 | 32 | public static NotationParserBuilder toType(Class resultingType) { 33 | return new NotationParserBuilder(new TypeInfo(resultingType)); 34 | } 35 | 36 | public static NotationParserBuilder toType(TypeInfo resultingType) { 37 | return new NotationParserBuilder(resultingType); 38 | } 39 | 40 | private NotationParserBuilder(TypeInfo resultingType) { 41 | this.resultingType = resultingType; 42 | typeDisplayName = resultingType.getTargetType().equals(String.class) ? "a String" : String.format("an object of type %s", resultingType.getTargetType().getSimpleName()); 43 | } 44 | 45 | public NotationParserBuilder parser(NotationParser parser) { 46 | this.notationParsers.add(new NotationParserToNotationConverterAdapter(parser)); 47 | return this; 48 | } 49 | 50 | /** 51 | * Specifies the display name for the target type, to use in error messages. By default the target type's simple name is used. 52 | */ 53 | public NotationParserBuilder typeDisplayName(String name) { 54 | this.typeDisplayName = name; 55 | return this; 56 | } 57 | 58 | /** 59 | * Use only those converters that are explicitly registered, and disable any implicit conversion that may normally be done. 60 | */ 61 | public NotationParserBuilder noImplicitConverters() { 62 | implicitConverters = false; 63 | return this; 64 | } 65 | 66 | /** 67 | * Allow null as a valid input. The default is to disallow null. 68 | * 69 | *

When this is enabled, all converters must be null safe. 70 | * 71 | * TODO - attach the null safety to each converter and infer whether null is a valid input or not. 72 | */ 73 | public NotationParserBuilder allowNullInput() { 74 | allowNullInput = true; 75 | return this; 76 | } 77 | 78 | /** 79 | * Adds a converter to use to parse notations. Converters are used in the order added. 80 | */ 81 | public NotationParserBuilder converter(NotationConverter converter) { 82 | this.notationParsers.add(converter); 83 | return this; 84 | } 85 | 86 | /** 87 | * Adds a converter that accepts only notations of the given type. 88 | */ 89 | public NotationParserBuilder fromType(Class notationType, NotationConverter converter) { 90 | this.notationParsers.add(new TypeFilteringNotationConverter(notationType, converter)); 91 | return this; 92 | } 93 | 94 | /** 95 | * Adds a converter that accepts any CharSequence notation. 96 | */ 97 | public NotationParserBuilder fromCharSequence(NotationConverter converter) { 98 | this.notationParsers.add(new CharSequenceNotationConverter(converter)); 99 | return this; 100 | } 101 | 102 | /** 103 | * Adds a converter that accepts any CharSequence notation. Can only be used when the target type is String. 104 | */ 105 | public NotationParserBuilder fromCharSequence() { 106 | if (!resultingType.getTargetType().equals(String.class)) { 107 | throw new UnsupportedOperationException("Can only convert from CharSequence when the target type is String."); 108 | } 109 | NotationConverter notationParser = new CharSequenceNotationParser(); 110 | fromCharSequence(notationParser); 111 | return this; 112 | } 113 | 114 | public NotationParserBuilder invalidNotationMessage(String invalidNotationMessage) { 115 | this.invalidNotationMessage = invalidNotationMessage; 116 | return this; 117 | } 118 | 119 | public NotationParserBuilder parsers(Iterable> notationParsers) { 120 | for (NotationParser parser : notationParsers) { 121 | parser(parser); 122 | } 123 | return this; 124 | } 125 | 126 | public NotationParser> toFlatteningComposite() { 127 | return wrapInErrorHandling(new FlatteningNotationParser(create())); 128 | } 129 | 130 | public NotationParser toComposite() { 131 | return wrapInErrorHandling(create()); 132 | } 133 | 134 | private NotationParser wrapInErrorHandling(NotationParser parser) { 135 | return new ErrorHandlingNotationParser(typeDisplayName, invalidNotationMessage, allowNullInput, parser); 136 | } 137 | 138 | private NotationParser create() { 139 | List> composites = new LinkedList>(); 140 | if (!resultingType.getTargetType().equals(Object.class) && implicitConverters) { 141 | composites.add(new NotationParserToNotationConverterAdapter(new JustReturningParser(resultingType.getTargetType()))); 142 | } 143 | composites.addAll(this.notationParsers); 144 | 145 | return new NotationConverterToNotationParserAdapter(new CompositeNotationConverter(composites)); 146 | } 147 | 148 | private static class NotationParserToNotationConverterAdapter implements NotationConverter { 149 | private final NotationParser parser; 150 | 151 | private NotationParserToNotationConverterAdapter(NotationParser parser) { 152 | this.parser = parser; 153 | } 154 | 155 | public void convert(N notation, NotationConvertResult result) throws TypeConversionException { 156 | T t; 157 | try { 158 | t = parser.parseNotation(notation); 159 | } catch (UnsupportedNotationException e) { 160 | return; 161 | } 162 | result.converted(t); 163 | } 164 | 165 | public void describe(Collection candidateFormats) { 166 | parser.describe(candidateFormats); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/TimeUnitsParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import org.gradle.api.InvalidUserDataException; 20 | 21 | import java.util.concurrent.TimeUnit; 22 | 23 | import static com.prezi.haskell.gradle.incubating.typeconversion.NormalizedTimeUnit.millis; 24 | 25 | public class TimeUnitsParser { 26 | 27 | public NormalizedTimeUnit parseNotation(CharSequence notation, int value) { 28 | String candidate = notation.toString().toUpperCase(); 29 | //jdk5 does not have days, hours or minutes, normalizing to millis 30 | if (candidate.equals("DAYS")) { 31 | return millis(value * 24 * 60 * 60 * 1000); 32 | } else if (candidate.equals("HOURS")) { 33 | return millis(value * 60 * 60 * 1000); 34 | } else if (candidate.equals("MINUTES")) { 35 | return millis(value * 60 * 1000); 36 | } 37 | try { 38 | return new NormalizedTimeUnit(value, TimeUnit.valueOf(candidate)); 39 | } catch (Exception e) { 40 | throw new InvalidUserDataException("Unable to parse provided TimeUnit: " + notation, e); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/TypeConversionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | /** 20 | * Thrown when a given value cannot be converted to the target type. 21 | */ 22 | public class TypeConversionException extends RuntimeException { 23 | public TypeConversionException(String message) { 24 | super(message); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/TypeFilteringNotationConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.util.Collection; 20 | 21 | class TypeFilteringNotationConverter implements NotationConverter { 22 | private final Class type; 23 | private final NotationConverter delegate; 24 | 25 | public TypeFilteringNotationConverter(Class type, NotationConverter delegate) { 26 | this.type = type; 27 | this.delegate = delegate; 28 | } 29 | 30 | public void convert(N notation, NotationConvertResult result) throws TypeConversionException { 31 | if (type.isInstance(notation)) { 32 | delegate.convert(type.cast(notation), result); 33 | } 34 | } 35 | 36 | public void describe(Collection candidateFormats) { 37 | delegate.describe(candidateFormats); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/TypeInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | /** 20 | * Type literal, useful for nested Generics. 21 | */ 22 | public class TypeInfo { 23 | private final Class targetType; 24 | 25 | public TypeInfo(Class targetType) { 26 | assert targetType != null; 27 | this.targetType = targetType; 28 | } 29 | 30 | public Class getTargetType() { 31 | return targetType; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/TypedNotationParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.util.Collection; 20 | 21 | public abstract class TypedNotationParser implements NotationParser { 22 | 23 | private final Class typeToken; 24 | 25 | public TypedNotationParser(Class typeToken) { 26 | assert typeToken != null : "typeToken cannot be null"; 27 | this.typeToken = typeToken; 28 | } 29 | 30 | public TypedNotationParser(TypeInfo typeToken) { 31 | assert typeToken != null : "typeToken cannot be null"; 32 | this.typeToken = typeToken.getTargetType(); 33 | } 34 | 35 | public void describe(Collection candidateFormats) { 36 | candidateFormats.add(String.format("Instances of %s.", typeToken.getSimpleName())); 37 | } 38 | 39 | public T parseNotation(Object notation) { 40 | if (!typeToken.isInstance(notation)) { 41 | throw new UnsupportedNotationException(notation); 42 | } 43 | return parseType(typeToken.cast(notation)); 44 | } 45 | 46 | abstract protected T parseType(N notation); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/UnsupportedNotationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.prezi.haskell.gradle.incubating.typeconversion; 17 | 18 | import org.gradle.api.Nullable; 19 | import org.gradle.util.GUtil; 20 | 21 | import java.util.Formatter; 22 | import java.util.List; 23 | 24 | public class UnsupportedNotationException extends RuntimeException { 25 | private final Object notation; 26 | 27 | public UnsupportedNotationException(Object notation) { 28 | this.notation = notation; 29 | } 30 | 31 | public UnsupportedNotationException(Object notation, String failure, @Nullable String resolution, List candidateTypes) { 32 | super(format(failure, resolution, candidateTypes)); 33 | this.notation = notation; 34 | } 35 | 36 | private static String format(String failure, String resolution, List formats) { 37 | Formatter message = new Formatter(); 38 | message.format("%s%n", failure); 39 | message.format("The following types/formats are supported:"); 40 | for (String format : formats) { 41 | message.format("%n - %s", format); 42 | } 43 | if (GUtil.isTrue(resolution)) { 44 | message.format("%n%n%s", resolution); 45 | } 46 | return message.toString(); 47 | } 48 | 49 | public Object getNotation() { 50 | return notation; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/prezi/haskell/gradle/incubating/typeconversion/ValueAwareNotationParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.prezi.haskell.gradle.incubating.typeconversion; 18 | 19 | import java.util.Collection; 20 | 21 | public interface ValueAwareNotationParser extends NotationParser { 22 | void describeValues(Collection collector); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/haskell.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.prezi.haskell.gradle.HaskellPlugin 2 | -------------------------------------------------------------------------------- /src/main/resources/com/prezi/haskell/gradle/tasks/SandFix.hs: -------------------------------------------------------------------------------- 1 | ../../../../../../../../sandfix/src/SandFix.hs -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/ApiHelper.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle 2 | 3 | import java.io.File 4 | 5 | import groovy.lang.Closure 6 | import org.gradle.api.{Project, Action} 7 | import org.gradle.api.artifacts.ModuleVersionIdentifier 8 | import org.gradle.api.artifacts.component.{ProjectComponentIdentifier, ModuleComponentIdentifier, ComponentIdentifier} 9 | import org.gradle.internal.reflect.Instantiator 10 | 11 | import scala.language.implicitConversions 12 | import scala.reflect.ClassTag 13 | 14 | /** 15 | * Helper functions for better interop with Gradle and/or Groovy 16 | */ 17 | object ApiHelper { 18 | 19 | implicit def asAction[T](action: T => Unit): Action[T] = 20 | new Action[T] { 21 | override def execute(t: T): Unit = action(t) 22 | } 23 | 24 | implicit def asClosure[T](fun: T => Unit): Closure[T] = 25 | new Closure[T](()) { 26 | protected def doCall(args: AnyRef): AnyRef = { 27 | fun(args.asInstanceOf[T]) 28 | null 29 | } 30 | } 31 | 32 | implicit def asClosureWithReturn[A, B<:AnyRef](fun: A => B): Closure[A] = 33 | new Closure[A](()) { 34 | protected def doCall(args: AnyRef): AnyRef = { 35 | fun(args.asInstanceOf[A]) 36 | } 37 | } 38 | 39 | implicit def instantiatorExt(instantiator: Instantiator): InstantiatorExt = 40 | new InstantiatorExt(instantiator) 41 | 42 | class InstantiatorExt(instantiator: Instantiator) { 43 | def create[T](params: Object*)(implicit t: ClassTag[T]): T = { 44 | instantiator.newInstance[T](t.runtimeClass.asInstanceOf[Class[T]], params : _*) 45 | } 46 | } 47 | 48 | implicit def fileExt(file: File): FileExt = new FileExt(file) 49 | 50 | class FileExt(file: File) { 51 | def (subPath: String): File = new File(file, subPath) 52 | } 53 | } 54 | 55 | case class ModuleId(group: String, name: String, version: String) { 56 | def toDisplayName: String = 57 | s"$group-$name-$version" 58 | } 59 | 60 | case object ModuleId { 61 | def fromModuleVersionIdentifier(id: ModuleVersionIdentifier): ModuleId = 62 | ModuleId(id.getGroup, id.getName, id.getVersion) 63 | 64 | def fromComponentIdentifier(rootProject: Project, id: ComponentIdentifier): ModuleId = id match { 65 | case mcid: ModuleComponentIdentifier => 66 | ModuleId(mcid.getGroup, mcid.getModule, mcid.getVersion) 67 | case pcid: ProjectComponentIdentifier => 68 | val project = rootProject.findProject(pcid.getProjectPath) 69 | ModuleId(project.getGroup.asInstanceOf[String], project.getName, project.getVersion.asInstanceOf[String]) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/HaskellPlugin.scala: -------------------------------------------------------------------------------- 1 | 2 | package com.prezi.haskell.gradle 3 | 4 | import javax.inject.Inject 5 | 6 | import com.prezi.haskell.gradle.extension.HaskellProject 7 | import org.gradle.api.internal.file.FileResolver 8 | import org.gradle.api.plugins.BasePlugin 9 | import org.gradle.api.{Plugin, Project} 10 | import org.gradle.internal.reflect.Instantiator 11 | 12 | /** 13 | * Main entry point of the gradle-haskell-plugin 14 | * @param instantiator Gradle's object instantiator 15 | * @param fileResolver Gradle's file resolver 16 | */ 17 | class HaskellPlugin @Inject() (instantiator: Instantiator, fileResolver: FileResolver) extends Plugin[Project] { 18 | override def apply(project: Project): Unit = { 19 | project.getPlugins.apply(classOf[BasePlugin]) 20 | 21 | new HaskellProject(project, instantiator, fileResolver) 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/Names.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle 2 | 3 | /** 4 | * Names used by the plugin 5 | */ 6 | object Names { 7 | val mainConfiguration = "main" 8 | val testConfiguration = "test" 9 | 10 | val sandFixConfiguration = "sandfix" 11 | } 12 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/Profiling.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle 2 | 3 | object Profiling { 4 | 5 | def measureTime[T](fn : => T): (T, Double) = { 6 | val t0 = System.nanoTime() 7 | val result = fn 8 | val t1 = System.nanoTime() 9 | (result, (t1 - t0) * 1.0e-9) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/extension/HaskellCompilationSupport.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.extension 2 | 3 | import com.prezi.haskell.gradle.extension.impl.HaskellCompilationSupportImpl 4 | import org.gradle.api.Project 5 | import org.gradle.api.internal.file.FileResolver 6 | import org.gradle.internal.reflect.Instantiator 7 | 8 | /** 9 | * Adds source sets and compile tasks to a project 10 | * @param project The project the plugin is applied to 11 | * @param instantiator Gradle object instantiator 12 | * @param fileResolver File resolver, needed for the source sets 13 | */ 14 | class HaskellCompilationSupport( 15 | protected val project: Project, 16 | protected val instantiator: Instantiator, 17 | protected val fileResolver: FileResolver) 18 | extends HaskellCompilationSupportImpl with ProjectExtender { 19 | 20 | addSourceSets 21 | addCompileTasks 22 | addTestTasks 23 | addREPLTask 24 | addUpdateUpdateTask 25 | extendCleanTask 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/extension/HaskellExtension.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.extension 2 | 3 | import java.util 4 | 5 | import com.prezi.haskell.gradle.extension.HaskellExtension.PropertyKey 6 | import com.prezi.haskell.gradle.incubating.{DefaultProjectSourceSet, ProjectSourceSet} 7 | import com.prezi.haskell.gradle.model._ 8 | import org.gradle.api.{Action, GradleException, Project} 9 | import org.gradle.internal.reflect.Instantiator 10 | 11 | /** 12 | * Project extension holding the source set and other properties for Haskell projects 13 | * 14 | * @param instantiator Gradle object instantiator 15 | */ 16 | class HaskellExtension(instantiator: Instantiator, project: Project) extends java.io.Serializable { 17 | private val sources_ : ProjectSourceSet = instantiator.newInstance(classOf[DefaultProjectSourceSet], instantiator) 18 | private var profiling_ : Boolean = project.hasProperty(PropertyKey.GhcEnableProfiling) 19 | private var ghcVersion_ : String = "ghc-8.0.2" 20 | private var snapshotId_ : String = "lts-8.0" 21 | private var packageFlags_ : java.util.Map[String, java.util.Map[String, String]] = 22 | new util.HashMap[String, java.util.Map[String, String]]() 23 | private var isExecutable_ : Boolean = false 24 | private var overriddenSnapshotVersionsCacheDir_ : Option[String] = 25 | if (project.hasProperty(PropertyKey.SnapshotVersionsCacheDir)) Some(project.getProperties.get(PropertyKey.SnapshotVersionsCacheDir).toString) 26 | else None 27 | private var stackRoot_ : Option[String] = 28 | if (project.hasProperty(PropertyKey.StackRoot)) Some(project.getProperties.get(PropertyKey.StackRoot).toString) 29 | else None 30 | 31 | def getSources = sources_ 32 | 33 | def sources(action: Action[ProjectSourceSet]): Unit = { 34 | action.execute(sources_) 35 | } 36 | 37 | def getProfiling = profiling_ 38 | 39 | def setProfiling(value: Boolean): Unit = { 40 | profiling_ = value 41 | } 42 | 43 | def profiling(value: Boolean): Unit = setProfiling(value) 44 | 45 | def ghcVersion: String = ghcVersion_ 46 | 47 | def getGhcVersion: String = ghcVersion_ 48 | 49 | def setGhcVersion(value: String): Unit = { 50 | ghcVersion_ = value 51 | } 52 | 53 | def ghcVersion(value: String): Unit = setGhcVersion(value) 54 | 55 | def parsedGHCVersion: GHCVersion = { 56 | ghcVersion_ match { 57 | case "ghc-7.10.2" => 58 | GHC7102 59 | case "ghc-8.0.1" if System.getProperty("os.name") == "Mac OS X" => 60 | GHC801WithSierraFix 61 | case "ghc-8.0.1" => 62 | GHC801 63 | case "ghc-8.0.2" => 64 | GHC802 65 | case _ => 66 | throw new GradleException(s"Unsupported ghc version: $ghcVersion_") 67 | } 68 | } 69 | 70 | def snapshotId: String = snapshotId_ 71 | 72 | def getSnapshotId: String = snapshotId_ 73 | 74 | def setSnapshotId(value: String): Unit = { 75 | snapshotId_ = value 76 | } 77 | 78 | def snapshotId(value: String): Unit = setSnapshotId(value) 79 | 80 | def packageFlags = packageFlags_ 81 | 82 | def getPackageFlags = packageFlags_ 83 | 84 | def setPackageFlags(value: java.util.Map[String, java.util.Map[String, String]]): Unit = { 85 | packageFlags_ = value 86 | } 87 | 88 | def isExecutable = isExecutable_ 89 | 90 | def getIsExecutable = isExecutable_ 91 | 92 | def setIsExecutable(value: Boolean): Unit = { 93 | isExecutable_ = value 94 | } 95 | 96 | def overriddenSnapshotVersionsCacheDir = overriddenSnapshotVersionsCacheDir_ 97 | 98 | def getOverriddenSnapshotVersionsCacheDir = overriddenSnapshotVersionsCacheDir_ 99 | 100 | def setOverriddenSnapshotVersionsCacheDir(value: String) = { 101 | overriddenSnapshotVersionsCacheDir_ = Some(value) 102 | } 103 | 104 | def stackRoot = stackRoot_ 105 | 106 | def getStackRoot = stackRoot_ 107 | 108 | def setStackRoot(value: String) = { 109 | stackRoot_ = Some(value) 110 | } 111 | } 112 | 113 | object HaskellExtension { 114 | 115 | object PropertyKey { 116 | val GhcEnableProfiling = "ghc-enable-profiling" 117 | val SnapshotVersionsCacheDir = "snapshot-versions-dir" 118 | val StackRoot = "stack-root" 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/extension/HaskellProject.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.extension 2 | 3 | import com.prezi.haskell.gradle.extension.impl.HaskellProjectImpl 4 | import org.gradle.api.Project 5 | import org.gradle.api.internal.file.FileResolver 6 | import org.gradle.internal.reflect.Instantiator 7 | 8 | /** 9 | * Main entry point of the haskell plugin, 10 | * 11 | * Extends a gradle project with fields, configurations and tasks 12 | * @param project The project the plugin is applied on 13 | */ 14 | class HaskellProject( 15 | protected val project: Project, 16 | protected val instantiator: Instantiator, 17 | protected val fileResolver: FileResolver) 18 | extends HaskellProjectImpl with ProjectExtender { 19 | // Integrating haskell support to project 20 | registerExtension 21 | addFields 22 | addConfigurations 23 | addSandboxTasks 24 | addCompilation 25 | addStackSupport 26 | addArtifacts 27 | } 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/extension/ProjectExtender.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.extension 2 | 3 | import org.gradle.api.{Task, Project} 4 | import org.gradle.api.artifacts.Configuration 5 | 6 | import scala.collection.JavaConverters._ 7 | import scala.reflect.ClassTag 8 | 9 | /** 10 | * Helper functions for extending a project with new fields, configurations, etc. 11 | */ 12 | trait ProjectExtender { 13 | protected def project: Project 14 | 15 | def addConfiguration(configuration: String): Configuration = { 16 | val configs = project.getConfigurations 17 | val config = configs.create(configuration) 18 | configs.add(config) 19 | config 20 | } 21 | 22 | def addConfigurations(configurations: String*): Unit = { 23 | val configs = project.getConfigurations 24 | 25 | configs.addAll(configurations.map(configs.create).asJavaCollection) 26 | } 27 | 28 | def getConfiguration(name: String): Configuration = { 29 | val configs = project.getConfigurations 30 | configs.findByName(name) 31 | } 32 | 33 | def addField[T](name: String, value: T): Unit = { 34 | project.getExtensions.add(name, value) 35 | } 36 | 37 | def getField[T](name: String): T = { 38 | project.getExtensions.getByName(name).asInstanceOf[T] 39 | } 40 | 41 | def createTask[T <: Task](name: String)(implicit t: ClassTag[T]): T = { 42 | project.getTasks.create(name, t.runtimeClass.asInstanceOf[Class[T]]) 43 | } 44 | 45 | def getTask[T <: Task](name: String): T = { 46 | project.getTasks.getByName(name).asInstanceOf[T] 47 | } 48 | 49 | def isTaskDefined(name: String): Boolean = { 50 | project.getTasks.findByName(name) != null 51 | } 52 | 53 | def createField[T](name: String, params: Object*)(implicit t: ClassTag[T]): Unit = { 54 | project.getExtensions.create(name, t.runtimeClass.asInstanceOf[Class[T]], params : _*) 55 | } 56 | 57 | protected def haskellExtension: HaskellExtension = 58 | project.getExtensions.getByType(classOf[HaskellExtension]) 59 | } -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/extension/SandboxSupport.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.extension 2 | 3 | import java.io.File 4 | 5 | import com.prezi.haskell.gradle.extension.impl.SandboxSupportImpl 6 | import org.gradle.api.Project 7 | 8 | /** 9 | * Adds sandbox support for a project 10 | * 11 | * @param project The project the plugin is applied to 12 | */ 13 | class SandboxSupport(protected val project: Project, protected val sandFixDir: File) extends SandboxSupportImpl with ProjectExtender { 14 | 15 | addSandboxInfoTask 16 | addSandboxPackagesTask 17 | addStoreDependentSandboxesTask 18 | addCopySandFixTask 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/extension/StackSupport.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.extension 2 | 3 | import com.prezi.haskell.gradle.extension.impl.StackSupportImpl 4 | import org.gradle.api.Project 5 | 6 | class StackSupport(protected val project: Project) 7 | extends StackSupportImpl 8 | with ProjectExtender { 9 | 10 | addTasks 11 | extendCleanTask 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/extension/ZippedSandboxArtifactSupport.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.extension 2 | 3 | import com.prezi.haskell.gradle.extension.impl.ZippedSandboxArtifactSupportImpl 4 | import org.gradle.api.Project 5 | 6 | /** 7 | * Adds the project's sandbox as an artifact of the project 8 | */ 9 | class ZippedSandboxArtifactSupport(protected val project: Project) extends ZippedSandboxArtifactSupportImpl with ProjectExtender { 10 | 11 | defineZipSandboxArtifact 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/extension/impl/HaskellCompilationSupportImpl.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.extension.impl 2 | 3 | import com.prezi.haskell.gradle.ApiHelper._ 4 | import com.prezi.haskell.gradle.Names 5 | import com.prezi.haskell.gradle.extension.{HaskellExtension, ProjectExtender} 6 | import com.prezi.haskell.gradle.external.HaskellTools 7 | import com.prezi.haskell.gradle.incubating.ProjectSourceSet 8 | import com.prezi.haskell.gradle.model.DefaultHaskellSourceSet 9 | import com.prezi.haskell.gradle.tasks._ 10 | import org.gradle.api.internal.file.FileResolver 11 | import org.gradle.api.tasks.Delete 12 | import org.gradle.api.{DefaultTask, Task} 13 | import org.gradle.internal.reflect.Instantiator 14 | import org.gradle.language.base.plugins.LifecycleBasePlugin.{ASSEMBLE_TASK_NAME, CLEAN_TASK_NAME} 15 | 16 | import scala.collection.JavaConverters._ 17 | 18 | trait HaskellCompilationSupportImpl { 19 | this: ProjectExtender => 20 | 21 | protected def instantiator: Instantiator 22 | 23 | protected def fileResolver: FileResolver 24 | 25 | protected def addSourceSets(): Unit = { 26 | val mainSources = projectSourceSet.maybeCreate("main") 27 | val testSources = projectSourceSet.maybeCreate("test") 28 | 29 | val mainSourceSet = instantiator.create[DefaultHaskellSourceSet]("haskell", mainSources, fileResolver) 30 | val cabalSourceSet = instantiator.create[DefaultHaskellSourceSet]("cabal", mainSources, fileResolver) 31 | val testSourceSet = instantiator.create[DefaultHaskellSourceSet]("haskell", testSources, fileResolver) 32 | 33 | mainSourceSet.getSource.srcDir("src/main/haskell") 34 | mainSourceSet.getSource.include("**/*.hs") 35 | 36 | testSourceSet.getSource.srcDir("src/test/haskell") 37 | testSourceSet.getSource.include("**/*.hs") 38 | 39 | cabalSourceSet.getSource.srcDir(".") 40 | cabalSourceSet.getSource.include("*.cabal") 41 | 42 | mainSources.add(mainSourceSet) 43 | mainSources.add(cabalSourceSet) 44 | testSources.add(testSourceSet) 45 | } 46 | 47 | protected def addCompileTasks(): Unit = { 48 | for (conf <- project.getConfigurations.asScala) { 49 | val sourceSet = projectSourceSet.findByName(conf.getName) 50 | val assembleTask = getTask[Task](ASSEMBLE_TASK_NAME) 51 | 52 | if (sourceSet != null) { 53 | if (conf.getName == Names.mainConfiguration) { 54 | val compileConfTask = createTask[CompileTask]("compile" + conf.getName.capitalize) 55 | compileConfTask.attachToSourceSet(sourceSet) 56 | val parallelParameterName = "stack.parallelThreadCount" 57 | if (project.hasProperty(parallelParameterName)) { 58 | compileConfTask.setParallelThreadCount(Integer.valueOf(project.property(parallelParameterName).asInstanceOf[String])) 59 | } 60 | 61 | compileConfTask.configuration = Some(conf) 62 | compileConfTask.tools = tools 63 | 64 | assembleTask.dependsOn(compileConfTask) 65 | } else { 66 | val compileAliasTask = createTask[DefaultTask]("compile" + conf.getName.capitalize) 67 | 68 | compileAliasTask.dependsOn("compileMain") 69 | compileAliasTask.dependsOn(conf) 70 | 71 | assembleTask.dependsOn(compileAliasTask) 72 | } 73 | } 74 | } 75 | 76 | if (!isTaskDefined("build")) { 77 | val buildTask = createTask[DefaultTask]("build") 78 | buildTask.dependsOn(ASSEMBLE_TASK_NAME) 79 | buildTask.dependsOn("check") 80 | } 81 | } 82 | 83 | protected def addTestTasks(): Unit = { 84 | val testHaskellTask = createTask[TestTask]("testHaskell") 85 | testHaskellTask.dependsOn("compileTest") 86 | testHaskellTask.tools = tools 87 | testHaskellTask.configuration = Some(getConfiguration(Names.testConfiguration)) 88 | 89 | if (!isTaskDefined("test")) { 90 | val testTask = createTask[DefaultTask]("test") 91 | testTask.dependsOn(testHaskellTask) 92 | } else { 93 | getTask[Task]("test").dependsOn(testHaskellTask) 94 | } 95 | 96 | // The default `check` task just calls the `test` task 97 | val checkTask = 98 | if (!isTaskDefined("check")) { 99 | createTask[DefaultTask]("check") 100 | } else { 101 | getTask[Task]("check") 102 | } 103 | 104 | checkTask.getDependsOn.add(testHaskellTask) 105 | } 106 | 107 | protected def addREPLTask(): Unit = { 108 | val replTask = createTask[REPLTask]("repl") 109 | replTask.tools = tools 110 | replTask.configuration = Some(getConfiguration(Names.mainConfiguration)) 111 | } 112 | 113 | protected def addUpdateUpdateTask(): Unit = { 114 | val updateTask: HaskellDependencies with UsingHaskellTools = 115 | createTask[StackUpdateTask]("stackUpdate") 116 | 117 | updateTask.tools = tools 118 | updateTask.configuration = Some(getConfiguration(Names.mainConfiguration)) 119 | } 120 | 121 | protected def extendCleanTask(): Unit = { 122 | val cleanTask = getTask[Delete](CLEAN_TASK_NAME) 123 | cleanTask.delete(project.getProjectDir "dist") 124 | } 125 | 126 | private lazy val projectSourceSet: ProjectSourceSet = getField[HaskellExtension]("haskell").getSources 127 | private lazy val tools: Option[HaskellTools] = Some(getField[HaskellTools]("haskellTools")) 128 | } 129 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/extension/impl/HaskellProjectImpl.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.extension.impl 2 | 3 | import java.io.File 4 | 5 | import com.prezi.haskell.gradle.ApiHelper._ 6 | import com.prezi.haskell.gradle.Names 7 | import com.prezi.haskell.gradle.extension._ 8 | import com.prezi.haskell.gradle.external.{Git, HaskellTools} 9 | import com.prezi.haskell.gradle.io.packers.GradleZipPacker 10 | import com.prezi.haskell.gradle.model.{GHC802, StackYamlWriter} 11 | import com.prezi.haskell.gradle.model.sandboxstore.ProjectSandboxStore 12 | import org.gradle.api.internal.file.FileResolver 13 | import org.gradle.internal.reflect.Instantiator 14 | import resource._ 15 | 16 | trait HaskellProjectImpl { 17 | this: ProjectExtender => 18 | 19 | protected def instantiator: Instantiator 20 | 21 | protected def fileResolver: FileResolver 22 | 23 | val sandFixPath: File = project.getBuildDir "sandfix" 24 | 25 | val stackToolPath: File = project.getBuildDir "stack-tooling" 26 | 27 | // Helpers 28 | protected def addFields(): Unit = { 29 | 30 | val tools = new HaskellTools(project.exec, getStackToolPath()) 31 | val unpacker = new GradleZipPacker(project) 32 | val sandboxStore = new ProjectSandboxStore(project.getRootProject, Some(sandFixPath), unpacker, getField[HaskellExtension]("haskell"), tools) 33 | addField("haskellTools", tools) 34 | addField("sandboxStore", sandboxStore) 35 | 36 | val git = new Git(project.exec) 37 | addField("git", git) 38 | } 39 | 40 | protected def addConfigurations(): Unit = { 41 | val mainConfig = addConfiguration(Names.mainConfiguration) 42 | val testConfig = addConfiguration(Names.testConfiguration) 43 | 44 | testConfig.extendsFrom(mainConfig) 45 | } 46 | 47 | protected def addSandboxTasks(): Unit = { 48 | new SandboxSupport(project, sandFixPath) 49 | } 50 | 51 | protected def addCompilation(): Unit = { 52 | new HaskellCompilationSupport(project, instantiator, fileResolver) 53 | } 54 | 55 | protected def addArtifacts(): Unit = { 56 | new ZippedSandboxArtifactSupport(project) 57 | } 58 | 59 | protected def addStackSupport(): Unit = { 60 | new StackSupport(project) 61 | } 62 | 63 | protected def registerExtension(): Unit = { 64 | createField[HaskellExtension]("haskell", instantiator, project) 65 | } 66 | 67 | private def getStackToolPath(): File = { 68 | if (stackToolPath.exists() && (stackToolPath "stack.yaml").exists()) { 69 | stackToolPath 70 | } else { 71 | stackToolPath.mkdirs() 72 | for (yaml <- managed(new StackYamlWriter(stackToolPath "stack.yaml"))) { 73 | yaml.ghcVersion(haskellExtension.parsedGHCVersion) 74 | } 75 | stackToolPath 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/extension/impl/SandboxSupportImpl.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.extension.impl 2 | 3 | import java.io.File 4 | 5 | import com.prezi.haskell.gradle.Names 6 | import com.prezi.haskell.gradle.extension.ProjectExtender 7 | import com.prezi.haskell.gradle.external.HaskellTools 8 | import com.prezi.haskell.gradle.tasks._ 9 | 10 | trait SandboxSupportImpl { 11 | this: ProjectExtender => 12 | 13 | protected def addSandboxInfoTask(): Unit = { 14 | createTask[SandboxInfo]("sandboxInfo") 15 | } 16 | 17 | protected def addSandboxPackagesTask(): Unit = { 18 | val task = createTask[SandboxPackages]("sandboxPackages") 19 | task.tools = Some(getField[HaskellTools]("haskellTools")) 20 | task.configuration = Some(getConfiguration(Names.mainConfiguration)) 21 | } 22 | 23 | protected def addStoreDependentSandboxesTask(): Unit = { 24 | val configTask = createTask[ConfigureSandboxTasks]("configureSandboxes") 25 | configTask.configuration = Some(getConfiguration(Names.mainConfiguration)) 26 | 27 | val task = createTask[StoreDependentSandboxes]("storeDependentSandboxes") 28 | task.configuration = Some(getConfiguration(Names.mainConfiguration)) 29 | 30 | configTask.storeTask = Some(task) 31 | task.getDependsOn.add(configTask) 32 | } 33 | 34 | protected def sandFixDir : File 35 | 36 | protected def addCopySandFixTask(): Unit = { 37 | val task = createTask[CopySandFix]("copySandFix") 38 | task.sandFixDir = Some(sandFixDir) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/extension/impl/StackSupportImpl.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.extension.impl 2 | 3 | import com.prezi.haskell.gradle.ApiHelper._ 4 | import com.prezi.haskell.gradle.Names 5 | import com.prezi.haskell.gradle.extension.ProjectExtender 6 | import com.prezi.haskell.gradle.external.{Git, HaskellTools} 7 | import com.prezi.haskell.gradle.tasks.{GenerateStackYaml, StackPathTask} 8 | import org.gradle.api.tasks.Delete 9 | import org.gradle.language.base.plugins.LifecycleBasePlugin._ 10 | 11 | trait StackSupportImpl { 12 | this: ProjectExtender => 13 | 14 | protected def addTasks(): Unit = { 15 | val genTask = createTask[GenerateStackYaml]("generateStackYaml") 16 | genTask.targetFile = Some(project.getProjectDir "stack.yaml") 17 | genTask.configuration = Some(project.getConfigurations.getByName(Names.mainConfiguration)) 18 | genTask.tools = Some(getField[HaskellTools]("haskellTools")) 19 | genTask.git = Some(getField[Git]("git")) 20 | 21 | val pathTask = createTask[StackPathTask]("stackPath") 22 | pathTask.tools = Some(getField[HaskellTools]("haskellTools")) 23 | } 24 | 25 | protected def extendCleanTask(): Unit = { 26 | val cleanTask = getTask[Delete](CLEAN_TASK_NAME) 27 | cleanTask.delete(project.getProjectDir "stack.yaml") 28 | cleanTask.delete(project.getProjectDir ".stack-work") 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/extension/impl/ZippedSandboxArtifactSupportImpl.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.extension.impl 2 | 3 | import com.prezi.haskell.gradle.extension.ProjectExtender 4 | import com.prezi.haskell.gradle.tasks.ZippedSandbox 5 | 6 | trait ZippedSandboxArtifactSupportImpl { 7 | this: ProjectExtender => 8 | 9 | protected def defineZipSandboxArtifact(): Unit = { 10 | val zipSandbox = createTask[ZippedSandbox]("zipSandbox") 11 | project.getArtifacts.add("main", zipSandbox) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/external/Git.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.external 2 | 3 | import java.io.File 4 | import java.net.URL 5 | 6 | import org.gradle.api.Action 7 | import org.gradle.process.{ExecResult, ExecSpec} 8 | 9 | class Git(executor : Action[ExecSpec] => ExecResult) 10 | extends ToolsBase(executor) { 11 | 12 | def clone(repoUrl: URL, targetDir: File): Unit = { 13 | exec(None, Map.empty, "git", "clone", repoUrl.toString, targetDir.getAbsolutePath) 14 | } 15 | 16 | def pull(repoDir: File): Unit = { 17 | exec(Some(repoDir), Map.empty, "git", "pull") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/external/HaskellTools.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.external 2 | 3 | import java.io.File 4 | 5 | import com.prezi.haskell.gradle.model.Sandbox 6 | import org.gradle.api.Action 7 | import org.gradle.process.{ExecResult, ExecSpec} 8 | 9 | /** 10 | * Executes external tools with the Gradle project's execution command 11 | * 12 | * @param executor The `project.exec` function 13 | */ 14 | class HaskellTools(executor : Action[ExecSpec] => ExecResult, stackToolsDir: => File) 15 | extends ToolsBase(executor) { 16 | 17 | def ghc(stackRoot: Option[String], args: String*): Unit = 18 | stack( 19 | stackRoot, 20 | None, 21 | "ghc" +: "--" +: args : _*) 22 | 23 | def ghcPkgRecache(stackRoot: Option[String], sandbox: Sandbox): Unit = 24 | stack( 25 | stackRoot, 26 | None, 27 | "exec", 28 | "ghc-pkg", 29 | "--", 30 | "-f", 31 | sandbox.packageDb.getAbsolutePath, 32 | "recache") 33 | 34 | def ghcPkgList(stackRoot: Option[String], sandboxes: List[Sandbox]): Unit = 35 | stack( 36 | stackRoot, 37 | None, 38 | "ghc-pkg" +: "-- " +: "list" +: sandboxes.map(_.asPackageDbArg) : _*) 39 | 40 | def getCabalVersion(stackRoot: Option[String]): String = { 41 | val output = capturedStack( 42 | stackRoot, 43 | None, 44 | "exec", 45 | "ghc-pkg", 46 | "--", 47 | "describe", "Cabal" 48 | ) 49 | 50 | output 51 | .split('\n') 52 | .filter(_.startsWith("version: ")) 53 | .map(_.substring("version: ".length)) 54 | .head 55 | } 56 | 57 | def stack(stackRoot: Option[String], workingDir: Option[File], params: String*): Unit = { 58 | exec( 59 | workingDir.orElse(Some(stackToolsDir)), 60 | setStackRoot(stackRoot), 61 | "stack", 62 | "setup" 63 | ) 64 | unsafeStack(stackRoot, workingDir, params : _*) 65 | } 66 | 67 | def unsafeStack(stackRoot: Option[String], workingDir: Option[File], params: String*): Unit = 68 | exec( 69 | workingDir.orElse(Some(stackToolsDir)), 70 | setStackRoot(stackRoot), 71 | "stack", 72 | params: _* 73 | ) 74 | 75 | def capturedStack(stackRoot: Option[String], workingDir: Option[File], params: String*): String = { 76 | exec( 77 | workingDir.orElse(Some(stackToolsDir)), 78 | setStackRoot(stackRoot), 79 | "stack", 80 | "setup" 81 | ) 82 | capturedExec( 83 | workingDir.orElse(Some(stackToolsDir)), 84 | setStackRoot(stackRoot), 85 | "stack", 86 | params: _* 87 | ) 88 | } 89 | 90 | private def setStackRoot(stackRoot: Option[String]): Map[String, String] = { 91 | stackRoot match { 92 | case Some(root) => Map("STACK_ROOT" -> root) 93 | case None => Map.empty 94 | } 95 | } 96 | 97 | private def profilingArgs(profiling: Boolean): List[String] = { 98 | if (profiling) { 99 | List( 100 | "--enable-executable-profiling", 101 | "--enable-library-profiling" 102 | ) 103 | } else { 104 | List() 105 | } 106 | } 107 | 108 | private def onlyDepsArgs(onlyDeps: Boolean): List[String] = { 109 | if (onlyDeps) { 110 | List("--only-dependencies") 111 | } else { 112 | List() 113 | } 114 | } 115 | 116 | private def configFileArgs(configFile: Option[String]): List[String] = 117 | configFile match { 118 | case Some(cf) => List("--config-file", cf) 119 | case None => Nil 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/external/SandFix.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.external 2 | 3 | import java.io.{File, FileInputStream} 4 | 5 | import com.prezi.haskell.gradle.ApiHelper._ 6 | import com.prezi.haskell.gradle.model.Sandbox 7 | import org.apache.commons.codec.digest.DigestUtils 8 | import resource._ 9 | 10 | class SandFix(sandFixPath: File, haskellTools: HaskellTools) { 11 | private val sourceHash = calculateSourceHash() 12 | 13 | def run(sandbox: Sandbox, others: List[Sandbox], stackRoot: Option[String]): Unit = { 14 | val cabalVersion = haskellTools.getCabalVersion(stackRoot) 15 | val cacheDir = getCacheDir(cabalVersion, sourceHash) 16 | val cachedSandfix = getCachedFile(cacheDir) 17 | 18 | if (!cachedSandfix.exists()) { 19 | compileToCache(stackRoot, cacheDir) 20 | } 21 | 22 | val dbArgs = others.map(child => child.asPackageDbArg) 23 | val args = List( 24 | "exec", 25 | cachedSandfix.getAbsolutePath, 26 | "--", 27 | sandbox.root.getAbsolutePath, 28 | sandbox.packageDb.getName, 29 | "--package-db=global") ::: dbArgs 30 | 31 | haskellTools.stack(stackRoot, None, args : _*) 32 | } 33 | 34 | private def calculateSourceHash(): String = 35 | (managed(new FileInputStream(sandFixPath)) map DigestUtils.md5Hex).opt.get 36 | 37 | private def getCacheDir(cabalVersion: String, hash: String): File = 38 | new File(System.getProperty("user.home")) ".gradle-haskell" "sandfix-cache" s"$cabalVersion-$hash" 39 | 40 | private def compileToCache(stackRoot: Option[String], cacheDir: File): Unit = { 41 | if (!cacheDir.exists()) { 42 | cacheDir.mkdirs() 43 | } 44 | haskellTools.ghc(stackRoot, "-O2", "-o", getCachedFile(cacheDir).getAbsolutePath, sandFixPath.getAbsolutePath) 45 | } 46 | 47 | private def getCachedFile(cacheDir: File): File = cacheDir "sandfix" 48 | } 49 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/external/SnapshotVersions.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.external 2 | 3 | import java.io.File 4 | import java.net.URL 5 | 6 | import com.prezi.haskell.gradle.ApiHelper._ 7 | import org.gradle.api.{Action, GradleException} 8 | import org.gradle.process.{ExecResult, ExecSpec} 9 | 10 | /** 11 | * Wraps the `snapshot-versions` tool which lists all the dependencies read from 12 | * a cabal file with their corresponding versions fetched from a stackage snapshot. 13 | */ 14 | class SnapshotVersions(isOffline: Boolean, overriddenCacheDir: Option[File], stackRoot: Option[String], executor : Action[ExecSpec] => ExecResult, haskellTools: HaskellTools, git: Git) { 15 | 16 | def run(snapshot: String, cabal: File): Array[String] = { 17 | ensureToolExists() 18 | val output = haskellTools.capturedStack(stackRoot, Some(cacheDir), "exec", "snapshot-versions", "--", cabal.getAbsolutePath, snapshot, "--stack-yaml") 19 | 20 | output.split('\n').map(_.trim).filter(_.length > 0) 21 | } 22 | 23 | private def ensureToolExists(): Unit = { 24 | if (!cacheDir.exists) { 25 | fetchSource() 26 | buildSource() 27 | } 28 | else if (!isOffline) { 29 | updateSource() 30 | buildSource() 31 | } 32 | } 33 | 34 | private val cacheDir: File = 35 | overriddenCacheDir.getOrElse( 36 | new File(System.getProperty("user.home")) ".gradle-haskell" "snapshot-versions") 37 | 38 | private def fetchSource(): Unit = { 39 | if (!isOffline) { 40 | git.clone(new URL("https://github.com/vigoo/snapshot-versions.git"), cacheDir) 41 | } 42 | else { 43 | throw new GradleException("Cannot clone snapshot-versions in offline mode!") 44 | } 45 | } 46 | 47 | private def updateSource(): Unit = { 48 | if (!isOffline) { 49 | git.pull(cacheDir) 50 | } 51 | else { 52 | throw new GradleException("Cannot pull snapshot-versions in offline mode!") 53 | } 54 | } 55 | 56 | private def buildSource(): Unit = { 57 | // NOTE: while using the temporary backported Sierra fix, it is not safe to use the 58 | // stack executor here, as it installs the non-patched GHC to global. Replace this 59 | // back once GHC 8.0.2 is out: 60 | // haskellTools.stack(stackRoot, Some(cacheDir), "build") 61 | haskellTools.unsafeStack(None, None, "setup") // force stack-tools GHC version to be installed 62 | haskellTools.unsafeStack(stackRoot, Some(cacheDir), "build") 63 | //haskellTools.stack(stackRoot, Some(cacheDir), "build") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/external/ToolsBase.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.external 2 | 3 | import java.io.{ByteArrayOutputStream, File} 4 | 5 | import com.prezi.haskell.gradle.ApiHelper._ 6 | import org.gradle.api.Action 7 | import org.gradle.process.{ExecResult, ExecSpec} 8 | 9 | class ToolsBase(executor : Action[ExecSpec] => ExecResult) { 10 | protected def exec(workDir: Option[File], extraEnvironmentVariables: Map[String, String], program: String, args: String*): Unit = { 11 | executor(asAction({ spec: ExecSpec => 12 | val cmdLine = getCmdLine(workDir, program, args) 13 | spec.commandLine(cmdLine: _*) 14 | 15 | workDir map { spec.workingDir(_) } 16 | extraEnvironmentVariables.foreach { case (key, value) => 17 | spec.environment(key, value) 18 | } 19 | })) 20 | } 21 | 22 | protected def capturedExec(workDir: Option[File], extraEnvironmentVariables: Map[String, String], program: String, args: String*): String = { 23 | val stream = new ByteArrayOutputStream() 24 | 25 | executor(asAction({ spec: ExecSpec => 26 | val cmdLine = getCmdLine(workDir, program, args) 27 | spec.commandLine(cmdLine: _*) 28 | 29 | workDir map { spec.workingDir(_) } 30 | spec.setStandardOutput(stream) 31 | extraEnvironmentVariables.foreach { case (key, value) => 32 | spec.environment(key, value) 33 | } 34 | })) 35 | 36 | stream.toString 37 | } 38 | 39 | private def getCmdLine(workDir: Option[File], program: String, args: Seq[String]): Seq[String] = { 40 | program +: args 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/io/packers/GradleZipPacker.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.io.packers 2 | 3 | import java.io.File 4 | 5 | import com.prezi.haskell.gradle.ApiHelper._ 6 | import org.gradle.api.Project 7 | import org.gradle.api.file.CopySpec 8 | 9 | /** 10 | * ZIP unpacker implementation based on Gradle 11 | */ 12 | class GradleZipPacker(project: Project) extends Unpacker { 13 | 14 | override def unpack(zipFile: File, targetDir: File): Unit = { 15 | project.copy(asClosure { spec: CopySpec => 16 | spec.from(project.zipTree(zipFile)) 17 | spec.into(targetDir) 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/io/packers/Unpacker.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.io.packers 2 | 3 | import java.io.File 4 | 5 | /** 6 | * Abstract zip unpacker interface 7 | */ 8 | trait Unpacker { 9 | def unpack(zipFile: File, targetDir: File): Unit 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/model/GHCVersion.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.model 2 | 3 | sealed trait GHCVersion 4 | case object GHC7102 extends GHCVersion 5 | case object GHC801WithSierraFix extends GHCVersion 6 | case object GHC801 extends GHCVersion 7 | case object GHC802 extends GHCVersion 8 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/model/HaskellSourceSet.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.model 2 | 3 | import com.prezi.haskell.gradle.incubating.{FunctionalSourceSet, LanguageSourceSet, AbstractLanguageSourceSet} 4 | import org.gradle.api.internal.file.{DefaultSourceDirectorySet, FileResolver} 5 | import org.gradle.api.internal.file.collections.DefaultDirectoryFileTreeFactory; 6 | 7 | /** 8 | * Source set for Haskell projects 9 | */ 10 | trait HaskellSourceSet extends LanguageSourceSet { 11 | 12 | } 13 | 14 | class DefaultHaskellSourceSet(name: String, parentName: FunctionalSourceSet, fileResolver: FileResolver) 15 | extends AbstractLanguageSourceSet(name, parentName, "Haskell source", new DefaultSourceDirectorySet("source", fileResolver, new DefaultDirectoryFileTreeFactory())) 16 | with HaskellSourceSet { 17 | 18 | } -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/model/Sandbox.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.model 2 | 3 | import java.io.File 4 | 5 | import com.prezi.haskell.gradle.ApiHelper._ 6 | import com.prezi.haskell.gradle.tasks.StackPathTask 7 | import org.gradle.api.{GradleException, Project} 8 | 9 | import scala.io.Source 10 | 11 | abstract class Sandbox(val root: File) { 12 | 13 | val extractionRoot: File = root 14 | 15 | def packageDb: File 16 | 17 | def installPrefix: File 18 | 19 | val lock: File = root ".lock" 20 | 21 | def asPackageDbArg: String = s"--package-db=$packageDb" 22 | 23 | def asPrefixArg: String = s"--prefix=$installPrefix" 24 | 25 | override def toString: String = root.getAbsolutePath 26 | } 27 | 28 | object Sandbox { 29 | def createForProject(project: Project): Sandbox = { 30 | val pathCache = StackPathTask.getPathCache(project) 31 | if (!pathCache.exists) { 32 | throw new GradleException(s"Stack path cache (${pathCache.getAbsolutePath}) does not exists") 33 | } 34 | 35 | val key = "local-install-root: " 36 | val localInstallRoot = Source 37 | .fromFile(pathCache) 38 | .getLines() 39 | .find(_.startsWith(key)) 40 | .map(_.substring(key.length)) 41 | 42 | localInstallRoot match { 43 | case Some(path) => new StackSandbox(new File(path)) 44 | case None => throw new GradleException(s"Invalid 'stack path' output (${pathCache.getAbsolutePath})") 45 | } 46 | } 47 | 48 | } 49 | 50 | class StackSandbox(root: File) 51 | extends Sandbox(root) { 52 | 53 | val packageDb: File = root "pkgdb" 54 | val installPrefix: File = root 55 | } -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/model/SandboxArtifact.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.model 2 | 3 | import java.io.{FileInputStream, File} 4 | 5 | import com.prezi.haskell.gradle.ApiHelper._ 6 | import org.apache.commons.codec.digest.DigestUtils 7 | import org.apache.commons.io.FilenameUtils._ 8 | import resource._ 9 | 10 | case class SandboxArtifact(name: String, artifact: File) { 11 | 12 | private def calculateChecksum: String = 13 | (managed(new FileInputStream(artifact)) map DigestUtils.md5Hex).opt.get 14 | 15 | private val depsCabal = "deps" 16 | private val depsStack = "deps-stack" 17 | 18 | def toStackSandbox(root: File): Sandbox = 19 | new StackSandbox(root depsStack getBaseName(name) calculateChecksum) 20 | 21 | def toNormalizedString: String = name + "#" + artifact.getAbsoluteFile 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/model/StackOutputHash.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.model 2 | 3 | import java.io.{FileInputStream, FileOutputStream, PrintWriter, File} 4 | import java.nio.file.Paths 5 | import org.apache.commons.codec.digest.DigestUtils 6 | import org.apache.commons.io.FileUtils 7 | import resource._ 8 | import scala.collection.JavaConversions._ 9 | import scala.io.Source 10 | 11 | class StackOutputSnapshot(private val data: Map[String, String]) { 12 | def differsFrom(other: StackOutputSnapshot) = { 13 | !(data equals other.data) 14 | } 15 | 16 | def saveSnapshot(file: File): Unit = { 17 | val writer = new PrintWriter(new FileOutputStream(file)) 18 | try { 19 | for ((name, hash) <- data) { 20 | writer.print(name) 21 | writer.print(':') 22 | writer.print(hash) 23 | writer.println() 24 | } 25 | } 26 | finally { 27 | writer.close() 28 | } 29 | } 30 | } 31 | 32 | object StackOutputHash { 33 | def calculate(root: File): StackOutputSnapshot = { 34 | if (root.exists() && root.isDirectory) { 35 | val rootPath = Paths.get(root.getAbsolutePath) 36 | val pairs = 37 | for (file <- recursiveListFiles(root); 38 | name = rootPath.relativize(Paths.get(file.getAbsolutePath)).toString; 39 | hash = calculateHash(file) 40 | ) yield (name, hash) 41 | new StackOutputSnapshot(pairs.toMap) 42 | } else { 43 | new StackOutputSnapshot(Map.empty) 44 | } 45 | } 46 | 47 | def loadSnapshot(file: File): Option[StackOutputSnapshot] = { 48 | if (file.exists()) { 49 | val pairs = 50 | for (line <- Source.fromFile(file).getLines(); 51 | parts = line.split(':') if parts.length == 2; 52 | name = parts(0); 53 | hash = parts(1) 54 | ) yield (name, hash) 55 | Some(new StackOutputSnapshot(pairs.toMap)) 56 | } else { 57 | None 58 | } 59 | } 60 | 61 | private def recursiveListFiles(root: File): Stream[File] = 62 | FileUtils.listFiles(root, null, true).toStream.asInstanceOf[Stream[File]] 63 | 64 | private def calculateHash(file: File): String = 65 | (managed(new FileInputStream(file)) map DigestUtils.md5Hex).opt.get 66 | } 67 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/model/StackYamlWriter.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.model 2 | 3 | import java.io.File 4 | 5 | import org.apache.commons.io.FileUtils 6 | 7 | class StackYamlWriter(target: File) { 8 | val builder = new StringBuilder() 9 | 10 | def flags(pkgFlags: Map[String, Map[String, String]]): Unit = { 11 | builder.append("flags:\n") 12 | 13 | for ((pkgName, flags) <- pkgFlags) { 14 | builder.append(s" $pkgName:\n") 15 | for ((k, v) <- flags) { 16 | builder.append(s" $k: $v\n") 17 | } 18 | } 19 | } 20 | 21 | def packages(pkgs: Seq[String]): Unit = { 22 | builder.append("packages:") 23 | stringList(pkgs) 24 | } 25 | 26 | def extraPackageDbs(dbs: Seq[String]): Unit = { 27 | builder.append("extra-package-dbs:") 28 | stringList(dbs) 29 | } 30 | 31 | def extraDeps(deps: Seq[String]): Unit = { 32 | builder.append("extra-deps:") 33 | stringList(deps) 34 | } 35 | 36 | def localBinPath(path: String): Unit = { 37 | builder.append(s"local-bin-path: $path\n") 38 | } 39 | 40 | def ghcVersion(ghcVersion: GHCVersion): Unit = { 41 | ghcVersion match { 42 | case GHC7102 => 43 | builder.append("resolver: ghc-7.10.2\n") 44 | case GHC801 => 45 | builder.append("resolver: ghc-8.0.1\n") 46 | case GHC801WithSierraFix => 47 | // Temporary fix for https://github.com/commercialhaskell/stack/issues/2577 48 | builder.append( 49 | """ 50 | |resolver: ghc-8.0.1 51 | |compiler-check: match-exact 52 | |compiler: ghc-8.0.1.20161117 53 | |setup-info: 54 | | ghc: 55 | | linux64: 56 | | 8.0.1.20161117: 57 | | url: http://downloads.haskell.org/~ghc/8.0.2-rc1/ghc-8.0.1.20161117-x86_64-deb8-linux.tar.xz 58 | | content-length: 112047972 59 | | sha1: 6a6e4c9c53c71cc84b6966a9f61948542fd2f15a 60 | | macosx: 61 | | 8.0.1.20161117: 62 | | url: https://downloads.haskell.org/~ghc/8.0.2-rc1/ghc-8.0.1.20161117-x86_64-apple-darwin.tar.xz 63 | | content-length: 113379688 64 | | sha1: 53ed03d986a49ea680c291540ce44ce469514d7c 65 | | windows64: 66 | | 8.0.1.20161117: 67 | | url: https://downloads.haskell.org/~ghc/8.0.2-rc1/ghc-8.0.1.20161117-x86_64-unknown-mingw32.tar.xz 68 | | content-length: 155652048 69 | | sha1: 74118dd8fd8b5e4c69b25df1644273fbe13177c7 70 | """.stripMargin) 71 | case GHC802 => 72 | builder.append("resolver: ghc-8.0.2\n") 73 | } 74 | } 75 | 76 | def close(): Unit = { 77 | FileUtils.writeStringToFile(target, builder.toString()) 78 | } 79 | 80 | private def stringList(items: Seq[String]): Unit = { 81 | if (items.isEmpty) { 82 | builder.append(" []\n") 83 | } else { 84 | builder.append('\n') 85 | for (item <- items) { 86 | builder.append(s" - $item\n") 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/model/sandboxstore/ProjectSandboxStore.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.model.sandboxstore 2 | 3 | import java.io.File 4 | 5 | import com.prezi.haskell.gradle.ApiHelper._ 6 | import com.prezi.haskell.gradle.Profiling.measureTime 7 | import com.prezi.haskell.gradle.extension.HaskellExtension 8 | import com.prezi.haskell.gradle.external.{HaskellTools, SandFix} 9 | import com.prezi.haskell.gradle.io.packers.Unpacker 10 | import com.prezi.haskell.gradle.model.{Sandbox, SandboxArtifact} 11 | import org.gradle.api.{GradleException, Project} 12 | 13 | /** 14 | * Sandbox store implementation with the following properties 15 | * 16 | * - It stores the sandboxes in a subdirectory of a Gradle project's build directory 17 | * - Runs the SandFix tool on newly extracted sandboxes 18 | * 19 | * @param project The root project where the sandboxes will be stored 20 | * @param sandFixPath Path to the SandFix tool 21 | * @param unpacker Zip unpacker to be used to extract sandboxes 22 | * @param exts 23 | * @param tools 24 | */ 25 | class ProjectSandboxStore(project: Project, sandFixPath: Option[File], unpacker: Unpacker, exts: => HaskellExtension, tools: => HaskellTools) extends SandboxStore { 26 | 27 | private val root = project.getBuildDir 28 | private lazy val finalSandFixPath = sandFixPath.getOrElse(project.getBuildDir "sandfix") 29 | private lazy val sandFix = new SandFix(finalSandFixPath "SandFix.hs", tools) 30 | 31 | override def find(depSandbox: SandboxArtifact): Sandbox = 32 | depSandbox.toStackSandbox(root) 33 | 34 | override def get(depSandbox: SandboxArtifact): Sandbox = { 35 | val fixedSandbox = find(depSandbox) 36 | 37 | if (!fixedSandbox.root.exists()) 38 | throw new GradleException(s"Required sandbox ${depSandbox.name} was not found at ${fixedSandbox.root.getAbsolutePath}") 39 | 40 | fixedSandbox 41 | } 42 | 43 | override def store(stackRoot: Option[String], depSandbox: SandboxArtifact, dependencies: Set[SandboxArtifact]): SandBoxStoreResult = { 44 | val sandbox = find(depSandbox) 45 | if (!sandbox.root.exists()) { 46 | sandbox.root.mkdirs() 47 | 48 | project.getLogger.debug("Locking sandbox {}", sandbox.root.getAbsolutePath) 49 | if (sandbox.lock.createNewFile()) { 50 | try { 51 | extractSandbox(depSandbox, sandbox) 52 | fixSandbox(stackRoot, depSandbox, dependencies, sandbox) 53 | SandBoxStoreResult.Created 54 | } 55 | finally { 56 | project.getLogger.debug("Unlocking sandbox {}", sandbox.root.getAbsolutePath) 57 | sandbox.lock.delete() 58 | } 59 | } 60 | else { 61 | throw new GradleException(s"Could not store sandbox, store is locked by ${sandbox.lock.getAbsolutePath}") 62 | } 63 | } else { 64 | project.getLogger.info("Sandbox already exists at {}", sandbox.root.getAbsolutePath) 65 | SandBoxStoreResult.AlreadyExists 66 | } 67 | } 68 | 69 | def fixSandbox(stackRoot: Option[String], depSandbox: SandboxArtifact, dependencies: Set[SandboxArtifact], sandbox: Sandbox): Any = { 70 | // For executable artifacts we don't have a package db: 71 | if (sandbox.packageDb.exists()) { 72 | project.getLogger.info("Fixing dependent sandbox {}", depSandbox.name) 73 | 74 | val (_, elapsed) = measureTime { 75 | sandFix.run(sandbox, dependencies.map(get).toList, stackRoot) 76 | } 77 | project.getLogger.info("Fixed dependent sandbox {} in {} s", depSandbox.name, elapsed) 78 | 79 | val (_, elapsedRecache) = measureTime { 80 | tools.ghcPkgRecache(stackRoot, sandbox) 81 | } 82 | project.getLogger.info("ghc-pkg recache of sandbox {} in {} s", depSandbox.name, elapsedRecache) 83 | } 84 | } 85 | 86 | def extractSandbox(depSandbox: SandboxArtifact, sandbox: Sandbox): Unit = { 87 | project.getLogger.info("Extracting dependent sandbox {} to {}", depSandbox.name, sandbox.extractionRoot.getAbsolutePath) 88 | 89 | val (_, elapsed) = measureTime { 90 | unpacker.unpack(depSandbox.artifact, sandbox.extractionRoot) 91 | } 92 | 93 | project.getLogger.info(s"Extracted dependent sandbox ${depSandbox.name} to ${sandbox.extractionRoot.getAbsolutePath} in $elapsed s", List(): _*) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/model/sandboxstore/SandBoxStoreResult.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.model.sandboxstore 2 | 3 | sealed trait SandBoxStoreResult { 4 | def toNormalizedString:String 5 | } 6 | 7 | object SandBoxStoreResult { 8 | 9 | def apply(str:String): SandBoxStoreResult = { 10 | str match { 11 | case "Created" => Created 12 | case "AlreadyExists" => AlreadyExists 13 | case s => throw sys.error(s"Illegal SandBoxStoreResult: $s") 14 | } 15 | } 16 | 17 | case object Created extends SandBoxStoreResult { 18 | override def toNormalizedString: String = "Created" 19 | } 20 | case object AlreadyExists extends SandBoxStoreResult { 21 | override def toNormalizedString: String = "AlreadyExists" 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/model/sandboxstore/SandboxStore.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.model.sandboxstore 2 | 3 | import com.prezi.haskell.gradle.model.{Sandbox, SandboxArtifact} 4 | import org.gradle.api.GradleException 5 | 6 | trait SandboxStore { 7 | 8 | /** 9 | * Fixes and stores a sandbox artifact 10 | * @param depSandbox The sandbox artifact to be stored 11 | * @param dependencies Its dependencies, required to be able to fix the sandbox 12 | * @return Returns the outcome of the store opration (cached or it already existed) 13 | */ 14 | def store(stackRoot: Option[String], depSandbox: SandboxArtifact, dependencies: Set[SandboxArtifact]): SandBoxStoreResult 15 | 16 | /** 17 | * Gets the location for a given sandbox artifact where it would be stored. 18 | * 19 | * It does not require the sandbox to be actually in the store. 20 | * @param depSandbox The sandbox artifact to look for 21 | * @return Returns a sandbox descriptor with paths pointing to the store's appropriate subdirectory 22 | */ 23 | def find(depSandbox: SandboxArtifact): Sandbox 24 | 25 | /** 26 | * Gets the location for a given sandbox artifact where it would be stored. 27 | * 28 | * @throws GradleException if the given sandbox is not in the store 29 | * @param sandbox The sandbox artifact to look for 30 | * @return Returns a sandbox descriptor with paths pointing to the store's appropriate subdirectory 31 | */ 32 | def get(sandbox: SandboxArtifact): Sandbox 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/CompileTask.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import com.prezi.haskell.gradle.ApiHelper 4 | import com.prezi.haskell.gradle.ApiHelper._ 5 | import com.prezi.haskell.gradle.incubating.FunctionalSourceSet 6 | import com.prezi.haskell.gradle.model.StackOutputHash 7 | import org.gradle.api.artifacts.Configuration 8 | import org.gradle.api.file.{FileVisitDetails, FileVisitor} 9 | import org.gradle.api.tasks.{Input, TaskAction} 10 | 11 | import scala.collection.JavaConverters._ 12 | 13 | /** 14 | * Executes cabal install with the proper sandbox chaining 15 | */ 16 | class CompileTask 17 | extends StackExecTask 18 | with DependsOnStoreDependentSandboxes 19 | with TaskLogging { 20 | 21 | private var parallelThreadCount = 3 22 | 23 | dependsOn("generateStackYaml") 24 | 25 | def attachToSourceSet(sourceSet: FunctionalSourceSet): Unit = { 26 | 27 | for (lss <- sourceSet.asScala) { 28 | lss.getSource.visit(new FileVisitor { 29 | override def visitDir(fileVisitDetails: FileVisitDetails): Unit = { 30 | debug(s"$getName input dir: ${fileVisitDetails.getFile.getAbsolutePath}") 31 | getInputs.dir(fileVisitDetails.getFile) 32 | } 33 | 34 | override def visitFile(fileVisitDetails: FileVisitDetails): Unit = { 35 | debug(s"$getName input file: ${fileVisitDetails.getFile.getAbsolutePath}") 36 | getInputs.file(fileVisitDetails.getFile) 37 | } 38 | }) 39 | } 40 | 41 | dependsOn(sourceSet) 42 | 43 | getOutputs.upToDateWhen(ApiHelper.asClosureWithReturn { _: AnyRef => 44 | 45 | val snapshotPath = getProject.getBuildDir "stack-output-snapshot" 46 | debug(s"$getName Custom up to date check based on ${sandbox.root}, with snapshot file $snapshotPath") 47 | 48 | val oldHashes = StackOutputHash.loadSnapshot(snapshotPath) 49 | 50 | oldHashes match { 51 | case Some(hashes) => 52 | val newHashes = StackOutputHash.calculate(sandbox.root) 53 | newHashes.saveSnapshot(snapshotPath) 54 | 55 | val differs = hashes.differsFrom(newHashes) 56 | debug(s"$getName output snapshots differ: $differs") 57 | 58 | new java.lang.Boolean(!differs) 59 | case None => 60 | debug(s"$getName is not up to date, there were no saved output snapshot") 61 | java.lang.Boolean.FALSE 62 | } 63 | }) 64 | } 65 | 66 | override def onConfigurationSet(cfg: Configuration): Unit = { 67 | dependsOn(cfg) 68 | } 69 | 70 | @TaskAction 71 | def run(): Unit = { 72 | needsConfigurationSet 73 | needsToolsSet 74 | 75 | runWithStack() 76 | updateSnapshot() 77 | } 78 | 79 | private def runWithStack(): Unit = { 80 | val profilingArgs = if (useProfiling) { 81 | List("--executable-profiling", "--library-profiling") 82 | } else { 83 | List() 84 | } 85 | 86 | val ghcOptions = if (parallelThreadCount > 1) { 87 | List(s"""--ghc-options="-j$parallelThreadCount"""") 88 | } else { 89 | List() 90 | } 91 | 92 | tools.get.stack(stackRoot, Some(getProject.getProjectDir), 93 | "build" :: "--copy-bins" :: ghcOptions ::: profilingArgs: _*) 94 | } 95 | 96 | private def updateSnapshot(): Unit = { 97 | val snapshotPath = getProject.getBuildDir "stack-output-snapshot" 98 | val newHashes = StackOutputHash.calculate(sandbox.root) 99 | newHashes.saveSnapshot(snapshotPath) 100 | } 101 | 102 | @Input 103 | def getParallelThreadCount: Integer = parallelThreadCount 104 | 105 | def setParallelThreadCount(threadCount: Integer): Unit = { 106 | parallelThreadCount = threadCount 107 | } 108 | 109 | def parallelThreadCount(threadCount: Integer): Unit = setParallelThreadCount(threadCount) 110 | } 111 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/ConfigureSandboxTasks.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import com.prezi.haskell.gradle.Profiling.measureTime 4 | import org.gradle.api.DefaultTask 5 | import org.gradle.api.tasks.TaskAction 6 | import scala.collection.JavaConverters._ 7 | 8 | /** 9 | * Configures the @StoreDependentSandboxes task's inputs and outputs 10 | */ 11 | class ConfigureSandboxTasks 12 | extends DefaultTask 13 | with HaskellDependencies 14 | with TaskLogging { 15 | 16 | var storeTask: Option[StoreDependentSandboxes] = None 17 | 18 | @TaskAction 19 | def run(): Unit = { 20 | needsConfigurationSet 21 | 22 | if (storeTask.isEmpty) { 23 | throw new IllegalStateException("extractTask is not set") 24 | } 25 | 26 | val (artifacts, dt1) = measureTime { configuration.get.getResolvedConfiguration.getResolvedArtifacts.asScala } 27 | info(s"[PERFORMANCE] Getting resolved artifacts took $dt1 seconds") 28 | 29 | val (_, dt2) = measureTime { 30 | for (artifact <- artifacts) { 31 | storeTask.get.getInputs.file(artifact.getFile) 32 | 33 | debug(s"Adding ${artifact.getFile} as input for $storeTask") 34 | } 35 | } 36 | info(s"[PERFORMANCE] Setting storeTask's inputs took $dt2 seconds") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/CopySandFix.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import java.io._ 4 | 5 | import com.prezi.haskell.gradle.ApiHelper._ 6 | import org.apache.commons.io.IOUtils 7 | 8 | import org.gradle.api.DefaultTask 9 | import org.gradle.api.tasks.TaskAction 10 | 11 | import resource._ 12 | 13 | /** 14 | * Extracts the SandFix from the plugin's resources 15 | */ 16 | class CopySandFix extends DefaultTask { 17 | 18 | var sandFixDir: Option[File] = None 19 | 20 | @TaskAction 21 | def run(): Unit = { 22 | if (!sandFixDir.isDefined) { 23 | throw new IllegalStateException("sandFixDir is not specified") 24 | } 25 | 26 | sandFixDir.get.mkdirs() 27 | val sourceUrl = getClass.getClassLoader.getResource("com/prezi/haskell/gradle/tasks/SandFix.hs") 28 | 29 | for (src <- managed(sourceUrl.openStream()); 30 | dest <- managed(new FileOutputStream(sandFixDir.get "SandFix.hs"))) { 31 | IOUtils.copy(src, dest) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/DependsOnStoreDependentSandboxes.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import com.prezi.haskell.gradle.ApiHelper 4 | import org.gradle.api.{GradleException, Task} 5 | import scala.util.{Failure, Success, Try} 6 | import java.lang.Boolean 7 | 8 | /** 9 | * Defines dependency on the 'storeDependentSandboxes' task with a custom up-to-date check 10 | * based on the result of that task. 11 | */ 12 | trait DependsOnStoreDependentSandboxes { 13 | this: Task => 14 | 15 | dependsOn("storeDependentSandboxes") 16 | 17 | getOutputs.upToDateWhen (ApiHelper.asClosureWithReturn { _: AnyRef => 18 | (for { 19 | storeDependentSandboxesTask <- Try { getProject.getTasksByName("storeDependentSandboxes", false).iterator().next() } 20 | isAnySandboxUpdated <- Try { storeDependentSandboxesTask.asInstanceOf[StoreDependentSandboxes].isAnySandboxUpdated } 21 | } yield isAnySandboxUpdated) match { 22 | case Success(bool) => new Boolean(!bool) 23 | case Failure(e) => throw new GradleException("Failed to get storeDependentSandboxes.isAnySandboxUpdated!", e) 24 | } 25 | }) 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/GenerateStackYaml.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import java.io.File 4 | 5 | import com.prezi.haskell.gradle.ApiHelper._ 6 | import com.prezi.haskell.gradle.external.SnapshotVersions 7 | import com.prezi.haskell.gradle.model.{Sandbox, StackYamlWriter} 8 | import com.prezi.haskell.gradle.util.FileLock 9 | import org.gradle.api.tasks.TaskAction 10 | import org.gradle.api.{DefaultTask, GradleException} 11 | 12 | import scala.collection.JavaConverters._ 13 | import resource._ 14 | 15 | class GenerateStackYaml 16 | extends DefaultTask 17 | with HaskellProjectSupport 18 | with HaskellDependencies 19 | with UsingHaskellTools 20 | with UsingGit 21 | with DependsOnStoreDependentSandboxes 22 | with TaskLogging { 23 | 24 | private var targetFile_ : Option[File] = None 25 | 26 | findCabalFile() match { 27 | case Some(cabalFile) => getInputs.file(cabalFile) 28 | case None => 29 | } 30 | 31 | def targetFile = targetFile_ 32 | def targetFile_=(value: Option[File]): Unit = { 33 | targetFile_ = value 34 | 35 | if (value.isDefined) { 36 | getOutputs.file(value.get) 37 | } 38 | } 39 | 40 | @TaskAction 41 | def run(): Unit = { 42 | val fileLock = new FileLock(new File(getProject.getRootProject.getBuildDir, "generate-yaml.lock")) 43 | fileLock.lock() 44 | try { 45 | needsConfigurationSet 46 | needsGitSet 47 | needsToolsSet 48 | 49 | if (targetFile.isEmpty) { 50 | throw new IllegalStateException("targetFile is not specified") 51 | } 52 | 53 | debug(s"GenerateStackYaml dependentSandboxes: $dependentSandboxes") 54 | generateContent(targetFile.get, dependentSandboxes) 55 | } finally { 56 | fileLock.release() 57 | } 58 | } 59 | 60 | private def generateContent(target: File, sandboxes: List[Sandbox]): Unit = { 61 | for (builder <- managed(new StackYamlWriter(target))) { 62 | val pkgFlags = haskellExtension 63 | .getPackageFlags 64 | .asScala 65 | .toMap 66 | .filter { case (_, value) => !value.isEmpty } 67 | .map { case (key, value) => (key, value.asScala.toMap) } 68 | 69 | if (pkgFlags.nonEmpty) { 70 | builder.flags(pkgFlags) 71 | } 72 | 73 | builder.packages(List(".")) 74 | builder.extraPackageDbs(sandboxes.map(_.packageDb.getAbsolutePath)) 75 | 76 | 77 | val isOffline = getProject.getGradle.getStartParameter.isOffline 78 | val snapshotVersions = new SnapshotVersions(isOffline, haskellExtension.getOverriddenSnapshotVersionsCacheDir.map(path => new File(path)), haskellExtension.getStackRoot, getProject.exec, tools.get, git.get) 79 | val deps = snapshotVersions.run(haskellExtension.snapshotId, getCabalFile()) 80 | 81 | builder.extraDeps(deps) 82 | 83 | val binPath = getProject.getBuildDir "sandbox" "files" "bin" 84 | binPath.mkdirs() 85 | builder.localBinPath(binPath.getAbsolutePath) 86 | builder.ghcVersion(haskellExtension.parsedGHCVersion) 87 | } 88 | } 89 | 90 | private def findCabalFile(): Option[File] = 91 | getProject.getProjectDir.listFiles().find(_.getName.endsWith(".cabal")) 92 | 93 | private def getCabalFile(): File = 94 | findCabalFile() match { 95 | case Some(file) => file 96 | case None => throw new GradleException(s"Could not find any .cabal files in ${getProject.getRootDir.getAbsolutePath}") 97 | } 98 | } 99 | 100 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/HaskellDependencies.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import com.prezi.haskell.gradle.ModuleId 4 | import com.prezi.haskell.gradle.model.sandboxstore.SandboxStore 5 | import com.prezi.haskell.gradle.model.{Sandbox, SandboxArtifact} 6 | import org.gradle.api.Task 7 | import org.gradle.api.artifacts.Configuration 8 | import org.gradle.api.artifacts.component.ComponentIdentifier 9 | import org.gradle.api.artifacts.result.{DependencyResult, ResolvedDependencyResult} 10 | 11 | import scala.collection.JavaConverters._ 12 | 13 | /** 14 | * Mixin for tasks relying on haskell dependencies 15 | */ 16 | trait HaskellDependencies { 17 | this: Task => 18 | 19 | private var _configuration: Option[Configuration] = None 20 | def configuration = _configuration 21 | def configuration_= (value: Option[Configuration]): Unit = { 22 | _configuration match { 23 | case Some(cfg) => throw new IllegalStateException("configuration was already set to " + cfg.getName) 24 | case _ => 25 | } 26 | 27 | _configuration = value 28 | 29 | _configuration match { 30 | case Some(cfg) => onConfigurationSet(cfg) 31 | case _ => 32 | } 33 | } 34 | 35 | protected def onConfigurationSet(cfg: Configuration): Unit = { 36 | } 37 | 38 | protected def needsConfigurationSet: Unit = { 39 | if (configuration.isEmpty) { 40 | throw new IllegalStateException("configuration is not specified") 41 | } 42 | } 43 | 44 | protected def store: SandboxStore = getProject.getExtensions.findByName("sandboxStore").asInstanceOf[SandboxStore] 45 | 46 | private def collectSandboxes(node: DependencyResult, results: List[ComponentIdentifier]): List[ComponentIdentifier] = { 47 | node match { 48 | case resolvedDep: ResolvedDependencyResult => 49 | val childDeps = resolvedDep.getSelected.getDependencies.asScala 50 | val childIds = childDeps.foldLeft(List[ComponentIdentifier]()) { (r, d) => collectSandboxes(d, r) } 51 | results ::: childIds ::: List(resolvedDep.getSelected.getId) 52 | case _ => 53 | results 54 | } 55 | } 56 | 57 | protected def dependentSandboxes: List[Sandbox] = { 58 | val result = configuration.get.getIncoming.getResolutionResult 59 | val orderedIds = result.getRoot.getDependencies 60 | .asScala 61 | .foldLeft(List[ComponentIdentifier]()) { (results, dep) => collectSandboxes(dep, results) } 62 | .distinct 63 | 64 | val artifacts = configuration.get.getResolvedConfiguration.getResolvedArtifacts 65 | .asScala 66 | .map(artifact => (ModuleId.fromModuleVersionIdentifier(artifact.getModuleVersion.getId), artifact.getFile)) 67 | .toMap 68 | 69 | val zips = 70 | for (id <- orderedIds; 71 | modId = ModuleId.fromComponentIdentifier(getProject.getRootProject, id)) 72 | yield artifacts.get(modId) match { 73 | case Some(file) => 74 | val sandbox = store.find(new SandboxArtifact(modId.name, file)) 75 | if (sandbox.packageDb.exists()) { 76 | Some(sandbox) 77 | } else { 78 | None 79 | } 80 | case None => None 81 | } 82 | 83 | zips.filter(_.isDefined).map(_.get) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/HaskellProjectSupport.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import com.prezi.haskell.gradle.extension.HaskellExtension 4 | import org.gradle.api.Task 5 | 6 | /** 7 | * Mixin for tasks working on Haskell projects 8 | */ 9 | trait HaskellProjectSupport { 10 | this: Task => 11 | 12 | protected def haskellExtension: HaskellExtension = 13 | getProject.getExtensions.getByType(classOf[HaskellExtension]) 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/REPLTask.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import org.gradle.api.tasks.TaskAction 4 | 5 | /** 6 | * Executes cabal repl or stack ghci 7 | */ 8 | class REPLTask extends StackExecTask { 9 | 10 | dependsOn("compileMain") 11 | 12 | @TaskAction 13 | def run(): Unit = { 14 | needsConfigurationSet 15 | needsToolsSet 16 | 17 | // TODO: check how scalaConsole task works in the gradle scala plugin 18 | tools.get.stack(stackRoot, Some(getProject.getProjectDir), "ghci") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/SandboxDirectories.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.tasks.TaskAction 5 | 6 | /** 7 | * Creates the sandbox directory structure for a project 8 | */ 9 | class SandboxDirectories 10 | extends DefaultTask 11 | with HaskellProjectSupport 12 | with UsesSandbox 13 | with TaskLogging { 14 | 15 | getOutputs.dir(sandbox.packageDb) 16 | getOutputs.dir(sandbox.installPrefix) 17 | 18 | @TaskAction 19 | def run(): Unit = { 20 | 21 | debug(s"Creating directory ${sandbox.packageDb}") 22 | sandbox.packageDb.mkdirs() 23 | 24 | debug(s"Creating directory ${sandbox.installPrefix}") 25 | sandbox.installPrefix.mkdirs() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/SandboxInfo.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.tasks.TaskAction 5 | 6 | /** 7 | * Prints basic info about a project's sandbox 8 | */ 9 | class SandboxInfo extends DefaultTask with HaskellProjectSupport with UsesSandbox { 10 | 11 | @TaskAction 12 | def run(): Unit = { 13 | println(s"Project ${getProject.getName} GHC sandbox info") 14 | println() 15 | println(s"Package db: ${sandbox.packageDb}") 16 | println(s"Install location: ${sandbox.installPrefix}") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/SandboxPackages.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.tasks.TaskAction 5 | 6 | /** 7 | * Lists the packages in a project's sandbox, and all its dependent sandboxes 8 | */ 9 | class SandboxPackages 10 | extends DefaultTask 11 | with HaskellProjectSupport 12 | with HaskellDependencies 13 | with UsingHaskellTools 14 | with UsesSandbox 15 | with StackExecTask { 16 | 17 | @TaskAction 18 | def run(): Unit = { 19 | needsToolsSet 20 | needsConfigurationSet 21 | 22 | tools.get.ghcPkgList(stackRoot, dependentSandboxes.+:(sandbox)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/SandboxTask.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import com.prezi.haskell.gradle.ApiHelper._ 4 | import org.gradle.api.DefaultTask 5 | import org.gradle.api.tasks.TaskAction 6 | 7 | /** 8 | * Initializes the sandbox of a project 9 | */ 10 | class SandboxTask extends DefaultTask 11 | with HaskellProjectSupport 12 | with UsingHaskellTools 13 | with UsesSandbox { 14 | 15 | dependsOn("sandboxDirectories") 16 | 17 | @TaskAction 18 | def run(): Unit = { 19 | needsToolsSet 20 | 21 | if (!(sandbox.packageDb "package.cache").exists()) { 22 | tools.get.ghcPkgRecache(haskellExtension.stackRoot, sandbox) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/StackExecTask.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import org.gradle.api.DefaultTask 4 | 5 | /** 6 | * Trait for tasks that executes stack 7 | */ 8 | trait StackExecTask 9 | extends DefaultTask 10 | with HaskellProjectSupport 11 | with HaskellDependencies 12 | with UsingHaskellTools 13 | with UsesSandbox { 14 | 15 | protected lazy val stackRoot: Option[String] = haskellExtension.getStackRoot 16 | 17 | protected val useProfiling: Boolean = haskellExtension.getProfiling 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/StackPathTask.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import java.io.{File, PrintWriter} 4 | 5 | import com.google.common.io.Files 6 | import com.prezi.haskell.gradle.ApiHelper._ 7 | import org.gradle.api.{DefaultTask, Project} 8 | import org.gradle.api.tasks.TaskAction 9 | import resource._ 10 | 11 | /** 12 | * Execute stack path and store its result 13 | */ 14 | class StackPathTask extends DefaultTask 15 | with HaskellProjectSupport 16 | with UsingHaskellTools { 17 | 18 | val outputFile: File = StackPathTask.getPathCache(getProject) 19 | 20 | dependsOn("generateStackYaml") 21 | getInputs.file(getProject.getProjectDir "stack.yaml") 22 | getOutputs.file(outputFile) 23 | 24 | @TaskAction 25 | def run(): Unit = { 26 | needsToolsSet 27 | 28 | val output = tools.get.capturedStack(haskellExtension.getStackRoot, Some(getProject.getProjectDir), "path") 29 | Files.createParentDirs(outputFile) 30 | for (writer <- managed(new PrintWriter(outputFile))) { 31 | writer.write(output) 32 | } 33 | } 34 | } 35 | 36 | object StackPathTask { 37 | def getPathCache(project: Project): File = 38 | project.getBuildDir "stack-path.out" 39 | } -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/StackUpdateTask.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import org.gradle.api.tasks.TaskAction 4 | 5 | /** 6 | * Executes stack update 7 | */ 8 | class StackUpdateTask extends StackExecTask { 9 | 10 | @TaskAction 11 | def run(): Unit = { 12 | needsConfigurationSet 13 | needsToolsSet 14 | 15 | tools.get.stack(stackRoot, None, "update") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/StoreDependentSandboxes.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import java.io.File 4 | import java.util 5 | 6 | import com.prezi.haskell.gradle.Profiling.measureTime 7 | import com.prezi.haskell.gradle.model.SandboxArtifact 8 | import com.prezi.haskell.gradle.model.sandboxstore.SandBoxStoreResult 9 | import com.prezi.haskell.gradle.util.FileLock 10 | import org.gradle.api.DefaultTask 11 | import org.gradle.api.artifacts.{Configuration, ResolvedDependency} 12 | import org.gradle.api.logging.LogLevel 13 | import org.gradle.api.tasks.TaskAction 14 | 15 | import scala.collection.JavaConverters._ 16 | import scala.collection.mutable 17 | 18 | class StoreDependentSandboxes 19 | extends DefaultTask 20 | with HaskellDependencies 21 | with UsingHaskellTools 22 | with HaskellProjectSupport 23 | with TaskLogging { 24 | 25 | dependsOn("copySandFix") 26 | 27 | var sandFixPath: Option[File] = None 28 | 29 | private var isAnySandboxUpdated_ : Boolean = false 30 | def isAnySandboxUpdated: Boolean = isAnySandboxUpdated_ 31 | private def isAnySandboxUpdated_=(value: Boolean): Unit = { 32 | isAnySandboxUpdated_ = value 33 | debug(s"isAnySandboxUpdated: ${isAnySandboxUpdated_}") 34 | } 35 | 36 | override def onConfigurationSet(cfg: Configuration): Unit = { 37 | dependsOn(cfg) 38 | } 39 | 40 | @TaskAction 41 | def run(): Unit = { 42 | val fileLock = new FileLock(new File(getProject.getRootProject.getBuildDir, "store-sandbox.lock")) 43 | fileLock.lock() 44 | try { 45 | needsConfigurationSet 46 | 47 | info(s"Storing dependent sandboxes for ${getProject.getName}") 48 | val storeDependencyResults = 49 | for (dependency <- configuration.get.getResolvedConfiguration.getFirstLevelModuleDependencies.asScala) yield { 50 | storeDependency(dependency) 51 | } 52 | 53 | dumpSandboxStoreResults(storeDependencyResults) 54 | isAnySandboxUpdated = storeDependencyResults.exists { 55 | _._2 == SandBoxStoreResult.Created 56 | } 57 | } finally { 58 | fileLock.release() 59 | } 60 | } 61 | 62 | def storeDependency(dependency: ResolvedDependency, prefix: String = ""): (Set[SandboxArtifact], SandBoxStoreResult) = { 63 | info(s"${prefix}Dependency ${dependency.getName}") 64 | 65 | // Storing child dependencies 66 | val (depSandboxes: Set[SandboxArtifact], aggregatedStoreResult: SandBoxStoreResult) = 67 | foldStoreDependencyResults( 68 | (for { 69 | child <- dependency.getChildren.asScala 70 | storeDependencyResult <- List(storeDependency(child, prefix + " ")) 71 | } yield storeDependencyResult).toSet 72 | ) 73 | 74 | // Collecting all the dependent sandbox artifacts 75 | val (sandboxes, dt1) = measureTime { 76 | dependency.getAllModuleArtifacts 77 | .asScala 78 | .map(artifact => new SandboxArtifact(artifact.getName, artifact.getFile)) 79 | .toSet 80 | } 81 | info(s"${prefix}[PERFORMANCE] Collecting dependent sandbox artifacts for ${dependency.getName} took $dt1 seconds") 82 | 83 | // Storing the sandbox and its dependent sandboxes 84 | val (sandboxStoreResults, dt2) = measureTime { 85 | for (sandbox <- sandboxes) yield { 86 | val res = memoizedStoreDependencyInStore(sandbox, depSandboxes) 87 | debug(s"$res <- memoizedStoreDependencyInStore($sandbox, $depSandboxes)") 88 | res 89 | } 90 | } 91 | info(s"${prefix}[PERFORMANCE] Storing sandbox and its dependent sandboxes for ${dependency.getName} took $dt2 seconds") 92 | 93 | (sandboxes, sandboxStoreResults.foldLeft(aggregatedStoreResult)(aggregateStoreResult)) 94 | } 95 | 96 | def foldStoreDependencyResults(results: Set[(Set[SandboxArtifact], SandBoxStoreResult)]): (Set[SandboxArtifact], SandBoxStoreResult) = 97 | results.foldLeft((Set[SandboxArtifact](), SandBoxStoreResult.AlreadyExists.asInstanceOf[SandBoxStoreResult])) { 98 | case ((accSandboxes, accStoreResult), (sandbox, storeResult)) => 99 | (accSandboxes ++ sandbox, 100 | if (storeResult == SandBoxStoreResult.Created) SandBoxStoreResult.Created 101 | else accStoreResult) 102 | } 103 | 104 | def memoizedStoreDependencyInStore(sandbox: SandboxArtifact, depSandboxes: Set[SandboxArtifact]): SandBoxStoreResult = { 105 | synchronized { 106 | val sandboxStoreCache: util.Map[String, String] = 107 | getOrSetRootProjectProperty[util.Map[String, String]]( 108 | StoreDependentSandboxes.RootProjectPropSandboxStoreResult, 109 | new util.HashMap[String, String]() // this needs to be a java (not scala) collection, otherwise classloader related cast issues will happen 110 | ) 111 | 112 | val key = sandbox.toNormalizedString 113 | if (sandboxStoreCache.containsKey(key)) { 114 | SandBoxStoreResult(sandboxStoreCache.get(key)) 115 | } else { 116 | val (res, dt) = measureTime { store.store(haskellExtension.stackRoot, sandbox, depSandboxes) } 117 | info(s"[PERFORMANCE] Storing and fixing sandbox $sandbox took $dt seconds") 118 | sandboxStoreCache.put(key, res.toNormalizedString) 119 | res 120 | } 121 | } 122 | } 123 | 124 | def getOrSetRootProjectProperty[V <: Object](propertyName: String, propertyValue: => V): V = { 125 | val rootProject = getProject.getRootProject 126 | 127 | if (rootProject.hasProperty(propertyName)) { 128 | rootProject.getProperties.get(propertyName).asInstanceOf[V] 129 | } else { 130 | val value = propertyValue 131 | 132 | debug(s"Setting root project property: [key = $propertyName, value = $value]") 133 | 134 | rootProject.getExtensions.add(propertyName, value) 135 | value 136 | } 137 | } 138 | 139 | private def aggregateStoreResult(aggregatedStoreResult: SandBoxStoreResult, newResult: SandBoxStoreResult): SandBoxStoreResult = { 140 | if (aggregatedStoreResult == SandBoxStoreResult.Created || newResult == SandBoxStoreResult.Created) { 141 | SandBoxStoreResult.Created 142 | } else { 143 | SandBoxStoreResult.AlreadyExists 144 | } 145 | } 146 | 147 | private def dumpSandboxStoreResults(storeDependencyResults: mutable.Set[(Set[SandboxArtifact], SandBoxStoreResult)]): Unit = { 148 | if (getLogger.isDebugEnabled) { 149 | getLogger.log(LogLevel.DEBUG, "StoreDependencyResults:") 150 | storeDependencyResults foreach { case (sandboxArtifacts, storeResult) => 151 | debug(s"$sandboxArtifacts: $storeResult") 152 | } 153 | } 154 | } 155 | } 156 | 157 | object StoreDependentSandboxes { 158 | val RootProjectPropSandboxStore = "haskell.cache.sandboxStore" 159 | val RootProjectPropSandboxStoreResult = "haskell.cache.sandboxStoreResult" 160 | } -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/TaskLogging.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import org.gradle.api.Task 4 | import org.gradle.api.logging.LogLevel 5 | 6 | trait TaskLogging { 7 | this: Task => 8 | 9 | def debug(msg: String) = 10 | getLogger.log(LogLevel.DEBUG, msg) 11 | 12 | def info(msg: String) = 13 | getLogger.log(LogLevel.INFO, msg) 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/TestTask.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import org.gradle.api.tasks.TaskAction 4 | 5 | /** 6 | * Executes cabal/stack test with the proper sandbox chaining 7 | */ 8 | class TestTask extends StackExecTask { 9 | 10 | dependsOn("generateStackYaml") 11 | 12 | @TaskAction 13 | def run(): Unit = { 14 | needsConfigurationSet 15 | needsToolsSet 16 | 17 | val profilingArgs = if (useProfiling) { 18 | List("--executable-profiling", "--library-profiling") 19 | } else { 20 | List() 21 | } 22 | tools.get.stack(stackRoot, Some(getProject.getProjectDir), "test" :: profilingArgs: _*) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/UsesSandbox.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import com.prezi.haskell.gradle.ApiHelper._ 4 | import com.prezi.haskell.gradle.model.Sandbox 5 | import java.io.File 6 | import org.gradle.api.Task 7 | 8 | trait UsesSandbox { 9 | this: Task with HaskellProjectSupport => 10 | 11 | dependsOn("stackPath") 12 | 13 | protected lazy val sandbox: Sandbox = 14 | Sandbox.createForProject(getProject) 15 | 16 | protected lazy val configTimeSandboxRoot: File = 17 | getProject.getProjectDir ".stack-work" "install" 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/UsingGit.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import com.prezi.haskell.gradle.external.Git 4 | import org.gradle.api.Task 5 | 6 | /** 7 | * Mixin for tasks requiring a @Git instance to work with 8 | */ 9 | trait UsingGit{ 10 | this: Task => 11 | 12 | var git: Option[Git] = None 13 | 14 | protected def needsGitSet: Unit = { 15 | if (!git.isDefined) { 16 | throw new IllegalStateException("git is not specified") 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/UsingHaskellTools.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import com.prezi.haskell.gradle.external.HaskellTools 4 | import org.gradle.api.Task 5 | 6 | /** 7 | * Mixin for tasks requiring a @HaskellTools instance to work with 8 | */ 9 | trait UsingHaskellTools { 10 | this: Task => 11 | 12 | var tools: Option[HaskellTools] = None 13 | 14 | protected def needsToolsSet: Unit = { 15 | if (!tools.isDefined) { 16 | throw new IllegalStateException("tools is not specified") 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/tasks/ZippedSandbox.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.tasks 2 | 3 | import com.prezi.haskell.gradle.ApiHelper._ 4 | import org.gradle.api.internal.file.copy.CopyAction 5 | import org.gradle.api.tasks.bundling.Zip 6 | 7 | /** 8 | * Zips the projects sandbox to create its main artifact 9 | */ 10 | class ZippedSandbox 11 | extends Zip 12 | with HaskellProjectSupport 13 | with UsesSandbox 14 | with TaskLogging { 15 | 16 | getDependsOn.addAll(getProject.getTasksByName("compileMain", false)) 17 | getInputs.sourceDir(configTimeSandboxRoot) 18 | 19 | override protected def createCopyAction: CopyAction = { 20 | if (haskellExtension.isExecutable) { 21 | val dir = getProject.getBuildDir "sandbox" 22 | debug(s"Zipping $dir") 23 | from(dir) 24 | } else { 25 | debug(s"Zipping ${sandbox.root}") 26 | from(sandbox.root) 27 | } 28 | super.createCopyAction 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/scala/com/prezi/haskell/gradle/util/FileLock.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.util 2 | 3 | import java.io.File 4 | 5 | import scala.util.Random 6 | 7 | class FileLock(file: File) { 8 | def lock(): Unit = { 9 | val random = new Random() 10 | file.getParentFile.mkdirs() 11 | // According to the javadoc, java.io.File.createNewFile should not be used for locking, but for this non-critical 12 | // use-case it is a good enough solution. 13 | while (!file.createNewFile()) 14 | Thread.sleep(random.nextInt(100)) 15 | } 16 | 17 | def release(): Unit = { 18 | file.delete() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/systest/scala/com/prezi/haskell/gradle/systests/BuildingInPrideSpecs.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.systests 2 | 3 | import com.prezi.haskell.gradle.ApiHelper._ 4 | import org.junit.runner.RunWith 5 | import org.specs2.mutable.SpecificationWithJUnit 6 | import org.specs2.runner.JUnitRunner 7 | 8 | @RunWith(classOf[JUnitRunner]) 9 | class BuildingInPrideSpecs 10 | extends SpecificationWithJUnit 11 | with UsingTestProjects 12 | with UsingPride { 13 | 14 | sequential 15 | 16 | "build in stack mode" should { 17 | "be able to build 'app' from clean state" in { 18 | withCleanWorkingDir("test1-pride") { root => 19 | initWorkspace(root) 20 | 21 | gradle(root, "app:build") aka "gradle app:build runs successfully" must beTrue 22 | appOutputExists(root) aka "build result exists" must beTrue 23 | runApp(root) aka "the compiled app's output" must be equalTo "hello world" 24 | } 25 | } 26 | 27 | "recompile succesfully if 'lib1' is changed" in { 28 | withCleanWorkingDir("test1-pride") { root => 29 | initWorkspace(root) 30 | 31 | gradle(root, "app:build") aka "First gradle app:build runs successfully" must beTrue 32 | runApp(root) aka "the compiled app's output" must be equalTo "hello world" 33 | 34 | modifySource(root "lib1" "src" "main" "haskell" "Lib1.hs", "\"hello \"", "\"hey \"") 35 | 36 | gradle(root, "app:build") aka "Second gradle app:build runs successfully" must beTrue 37 | runApp(root) aka "the recompiled app's output" must be equalTo "hey world" 38 | } 39 | } 40 | 41 | "recompile succesfully if 'lib1' is changed and recompiled" in { 42 | withCleanWorkingDir("test1-pride") { root => 43 | initWorkspace(root) 44 | 45 | gradle(root, "app:build") aka "First gradle app:build runs successfully" must beTrue 46 | runApp(root) aka "the compiled app's output" must be equalTo "hello world" 47 | 48 | modifySource(root "lib1" "src" "main" "haskell" "Lib1.hs", "\"hello \"", "\"hey \"") 49 | 50 | gradle(root, "lib1:build") aka "gradle lib1:build runs successfully" must beTrue 51 | gradle(root, "app:build") aka "Second gradle app:build runs successfully" must beTrue 52 | runApp(root) aka "the recompiled app's output" must be equalTo "hey world" 53 | } 54 | } 55 | 56 | "recompile successfully if 'lib2' is changed" in { 57 | withCleanWorkingDir("test1-pride") { root => 58 | initWorkspace(root) 59 | 60 | gradle(root, "app:build") aka "First gradle app:build runs successfully" must beTrue 61 | runApp(root) aka "the compiled app's output" must be equalTo "hello world" 62 | 63 | modifySource(root "lib2" "src" "main" "haskell" "Lib2.hs", "text greeting", "text \"hi\"") 64 | 65 | gradle(root, "app:build") aka "Second gradle app:build runs successfully" must beTrue 66 | runApp(root) aka "the recompiled app's output" must be equalTo "hi" 67 | } 68 | } 69 | 70 | "recompile successfully if 'lib2' is changed and recompiled" in { 71 | withCleanWorkingDir("test1-pride") { root => 72 | initWorkspace(root) 73 | 74 | gradle(root, "app:build") aka "First gradle app:build runs successfully" must beTrue 75 | runApp(root) aka "the compiled app's output" must be equalTo "hello world" 76 | 77 | modifySource(root "lib2" "src" "main" "haskell" "Lib2.hs", "text greeting", "text \"hi\"") 78 | 79 | gradle(root, "lib2:build") aka "gradle lib2:build runs successfully" must beTrue 80 | gradle(root, "app:build") aka "Second gradle app:build runs successfully" must beTrue 81 | runApp(root) aka "the recompiled app's output" must be equalTo "hi" 82 | } 83 | } 84 | 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/systest/scala/com/prezi/haskell/gradle/systests/BuildingTestProjectSpecs.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.systests 2 | 3 | import com.prezi.haskell.gradle.ApiHelper._ 4 | import org.junit.runner.RunWith 5 | import org.specs2.mutable.SpecificationWithJUnit 6 | import org.specs2.runner.JUnitRunner 7 | 8 | @RunWith(classOf[JUnitRunner]) 9 | class BuildingTestProject1Specs extends BuildingTestProjectSpecsBase { 10 | val testName = "test1" 11 | val lib2ReplacePattern = "text greeting" 12 | } 13 | 14 | @RunWith(classOf[JUnitRunner]) 15 | class BuildingTestProject2Specs extends BuildingTestProjectSpecsBase { 16 | val testName = "test2" 17 | val lib2ReplacePattern = "text \\(hello greeting\\)" 18 | } 19 | 20 | trait BuildingTestProjectSpecsBase 21 | extends SpecificationWithJUnit 22 | with UsingTestProjects { 23 | 24 | def testName: String 25 | def lib2ReplacePattern: String 26 | 27 | sequential 28 | 29 | "test environment" should { 30 | "copy the test projects to a temporary location" in { 31 | withCleanWorkingDir(testName) { root => 32 | buildGradleExists(root) aka "build.gradle exists" must beTrue 33 | } 34 | } 35 | } 36 | 37 | "build in stack mode" should { 38 | "be able to build 'app' from clean state" in { 39 | withCleanWorkingDir(testName) { root => 40 | gradle(root, "app:build") aka "gradle app:build runs successfully" must beTrue 41 | appOutputExists(root) aka "build result exists" must beTrue 42 | runApp(root) aka "the compiled app's output" must be equalTo "hello world" 43 | } 44 | } 45 | 46 | "recompile successfully if 'lib1' is changed" in { 47 | withCleanWorkingDir(testName) { root => 48 | gradle(root, "app:build") aka "First gradle app:build runs successfully" must beTrue 49 | runApp(root) aka "the compiled app's output" must be equalTo "hello world" 50 | 51 | modifySource(root "lib1" "src" "main" "haskell" "Lib1.hs", "\"hello \"", "\"hey \"") 52 | 53 | gradle(root, "app:build") aka "Second gradle app:build runs successfully" must beTrue 54 | runApp(root) aka "the recompiled app's output" must be equalTo "hey world" 55 | } 56 | } 57 | 58 | "recompile successfully if 'lib1' is changed and recompiled" in { 59 | withCleanWorkingDir(testName) { root => 60 | gradle(root, "app:build") aka "First gradle app:build runs successfully" must beTrue 61 | runApp(root) aka "the compiled app's output" must be equalTo "hello world" 62 | 63 | modifySource(root "lib1" "src" "main" "haskell" "Lib1.hs", "\"hello \"", "\"hey \"") 64 | 65 | gradle(root, "lib1:build") aka "gradle lib1:build runs successfully" must beTrue 66 | gradle(root, "app:build") aka "Second gradle app:build runs successfully" must beTrue 67 | runApp(root) aka "the recompiled app's output" must be equalTo "hey world" 68 | } 69 | } 70 | 71 | "recompile successfully if 'lib2' is changed" in { 72 | withCleanWorkingDir(testName) { root => 73 | gradle(root, "app:build") aka "First gradle app:build runs successfully" must beTrue 74 | runApp(root) aka "the compiled app's output" must be equalTo "hello world" 75 | 76 | modifySource(root "lib2" "src" "main" "haskell" "Lib2.hs", lib2ReplacePattern, "text \"hi\"") 77 | 78 | gradle(root, "app:build") aka "Second gradle app:build runs successfully" must beTrue 79 | runApp(root) aka "the recompiled app's output" must be equalTo "hi" 80 | } 81 | } 82 | 83 | "recompile successfully if 'lib2' is changed and recompiled" in { 84 | withCleanWorkingDir(testName) { root => 85 | gradle(root, "app:build") aka "First gradle app:build runs successfully" must beTrue 86 | runApp(root) aka "the compiled app's output" must be equalTo "hello world" 87 | 88 | modifySource(root "lib2" "src" "main" "haskell" "Lib2.hs", lib2ReplacePattern, "text \"hi\"") 89 | 90 | gradle(root, "lib2:build") aka "gradle lib2:build runs successfully" must beTrue 91 | gradle(root, "app:build") aka "Second gradle app:build runs successfully" must beTrue 92 | runApp(root) aka "the recompiled app's output" must be equalTo "hi" 93 | } 94 | } 95 | 96 | "be able to build 'app' from clean state with 'text' as dependency" in { 97 | withCleanWorkingDir("test1-with-text") { root => 98 | gradle(root, "app:build") aka "gradle app:build runs successfully" must beTrue 99 | appOutputExists(root) aka "build result exists" must beTrue 100 | runApp(root) aka "the compiled app's output" must be equalTo "hello world" 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/systest/scala/com/prezi/haskell/gradle/systests/ChangingCabalFileSpecs.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.systests 2 | 3 | import com.prezi.haskell.gradle.ApiHelper._ 4 | import org.junit.runner.RunWith 5 | import org.specs2.mutable.SpecificationWithJUnit 6 | import org.specs2.runner.JUnitRunner 7 | 8 | @RunWith(classOf[JUnitRunner]) 9 | class ChangingCabalFileSpecs 10 | extends SpecificationWithJUnit 11 | with UsingTestProjects { 12 | 13 | sequential 14 | 15 | "changing the .cabal file" should { 16 | "trigger regeneration of stack.yaml" in { 17 | withCleanWorkingDir("basic") { root => 18 | gradle(root, "-Puse-stack", "generateStackYaml") 19 | stackYamlExists(root) aka "stack.yaml exists" must beTrue 20 | stackYamlLines(root) aka "initial stack.yaml has no extra-deps" must contain("extra-deps: []") 21 | 22 | modifySource(root "test.cabal", "base", "base, ansi-wl-pprint") 23 | 24 | gradle(root, "-Puse-stack", "generateStackYaml") 25 | stackYamlExists(root) aka "stack.yaml exists" must beTrue 26 | 27 | val lines = stackYamlLines(root) 28 | lines aka "rebuilt stack.yaml refers to ansi-terminal" must containMatch("ansi-terminal") 29 | lines aka "rebuilt stack.yaml refers to ansi-wl-pprint" must containMatch("ansi-wl-pprint") 30 | } 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/systest/scala/com/prezi/haskell/gradle/systests/StreamToStdout.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.systests 2 | 3 | import java.io.{BufferedReader, InputStream, InputStreamReader} 4 | 5 | class StreamToStdout(stream: InputStream) extends Thread { 6 | override def run(): Unit = { 7 | val reader = new BufferedReader(new InputStreamReader(stream)) 8 | try { 9 | var line: String = reader.readLine() 10 | while (line != null) { 11 | println(line) 12 | line = reader.readLine() 13 | } 14 | } 15 | finally { 16 | reader.close() 17 | } 18 | } 19 | } 20 | 21 | object StreamToStdout { 22 | def apply(stream: InputStream): Unit = { 23 | new StreamToStdout(stream).run() 24 | } 25 | } -------------------------------------------------------------------------------- /src/systest/scala/com/prezi/haskell/gradle/systests/UsingPride.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.systests 2 | 3 | import java.io.File 4 | import com.prezi.haskell.gradle.ApiHelper._ 5 | 6 | trait UsingPride { 7 | this: UsingTestProjects => 8 | 9 | protected def initPride(root: File): Unit = { 10 | val process = new ProcessBuilder() 11 | .command("pride", "init", "--force", "--gradle-version", "2.13") 12 | .directory(root) 13 | .start() 14 | 15 | StreamToStdout(process.getErrorStream) 16 | StreamToStdout(process.getInputStream) 17 | 18 | process.waitFor() == 0 19 | } 20 | 21 | protected def initWorkspace(root: File): Unit = { 22 | hardcodePluginBuildDir (root "lib1" "build.gradle") 23 | hardcodePluginBuildDir (root "lib2" "build.gradle") 24 | hardcodePluginBuildDir (root "app" "build.gradle") 25 | initPride (root) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/systest/scala/com/prezi/haskell/gradle/systests/UsingTestProjects.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.systests 2 | 3 | import java.io.{PrintWriter, BufferedReader, File, InputStreamReader} 4 | 5 | import com.google.common.io.Files 6 | import com.prezi.haskell.gradle.ApiHelper._ 7 | import org.apache.commons.io.FileUtils 8 | 9 | import scala.io.Source 10 | 11 | trait UsingTestProjects { 12 | 13 | private val testProjectsDirProp = System.getProperty("test-projects-dir") 14 | private val pluginBuildDirProp = System.getProperty("plugin-build-dir") 15 | 16 | protected def withCleanWorkingDir[R](testProjectName: String)(action: File => R): R = { 17 | val tempDir = Files.createTempDir() 18 | try { 19 | println(s"Using temporary directory ${tempDir.getAbsolutePath}") 20 | FileUtils.copyDirectory(new File(testProjectsDirProp) testProjectName, tempDir) 21 | 22 | action(tempDir) 23 | } 24 | finally { 25 | //FileUtils.deleteDirectory(tempDir) 26 | } 27 | } 28 | 29 | protected def buildGradleExists(path: File): Boolean = { 30 | println(s"Testing ${(path "build.gradle").getAbsolutePath}'s existance") 31 | (path "build.gradle").exists() 32 | } 33 | 34 | protected def gradle(root: File, args: String*): Boolean = { 35 | val process = new ProcessBuilder() 36 | .command("gradle" +: "--no-daemon" +: "--stacktrace" +: "-i" +: s"-PpluginBuildDir=$pluginBuildDirProp" +: args : _*) 37 | .directory(root) 38 | .start() 39 | 40 | StreamToStdout(process.getErrorStream) 41 | StreamToStdout(process.getInputStream) 42 | 43 | process.waitFor() == 0 44 | } 45 | 46 | protected def runApp(root: File): String = { 47 | val process = new ProcessBuilder() 48 | .command(appOutput(root).getAbsolutePath) 49 | .start() 50 | 51 | val reader = new BufferedReader(new InputStreamReader(process.getInputStream)) 52 | val result = reader.readLine() 53 | process.waitFor() 54 | result 55 | } 56 | 57 | protected def appOutputExists(path: File): Boolean = 58 | appOutput(path).exists() 59 | 60 | protected def appOutput(path: File): File = 61 | path "app" "build" "sandbox" "files" "bin" "app" 62 | 63 | protected def modifySource(path: File, regex: String, replacement: String): Unit = { 64 | val newContent = Source.fromFile(path).mkString.replaceAll(regex, replacement) 65 | val writer = new PrintWriter(path) 66 | try { 67 | writer.write(newContent) 68 | } 69 | finally { 70 | writer.close() 71 | } 72 | } 73 | 74 | protected def stackYamlExists(path: File): Boolean = 75 | (path "stack.yaml").exists() 76 | 77 | protected def stackYamlLines(path: File): List[String] = 78 | Source.fromFile(path "stack.yaml").getLines().toList 79 | 80 | protected def hardcodePluginBuildDir(path: File): Unit = { 81 | modifySource(path, "\\$pluginBuildDir", pluginBuildDirProp) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test-projects/basic/build.gradle: -------------------------------------------------------------------------------- 1 | pluginBuildDir = getProperty("pluginBuildDir") 2 | 3 | buildscript { 4 | dependencies { 5 | classpath fileTree(dir: "$pluginBuildDir/integ-test-bundle/lib", include: '*.jar') 6 | } 7 | } 8 | 9 | allprojects { 10 | apply plugin: "haskell" 11 | } 12 | -------------------------------------------------------------------------------- /src/test-projects/basic/src/main/haskell/Lib1.hs: -------------------------------------------------------------------------------- 1 | module Lib1 where 2 | 3 | hello :: String -> String 4 | hello name = "hello " ++ name 5 | -------------------------------------------------------------------------------- /src/test-projects/basic/test.cabal: -------------------------------------------------------------------------------- 1 | name: test 2 | version: 0.1.0.0 3 | author: Daniel Vigovszky 4 | maintainer: daniel.vigovszky@gmail.com 5 | build-type: Simple 6 | cabal-version: >=1.10 7 | 8 | library 9 | hs-source-dirs: src/main/haskell 10 | exposed-modules: Lib1 11 | build-depends: base 12 | default-language: Haskell2010 13 | -------------------------------------------------------------------------------- /src/test-projects/test1-pride/app/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /src/test-projects/test1-pride/app/app.cabal: -------------------------------------------------------------------------------- 1 | name: app 2 | version: 1.2.3.4 3 | author: Daniel Vigovszky 4 | maintainer: daniel.vigovszky@gmail.com 5 | build-type: Simple 6 | cabal-version: >=1.10 7 | 8 | executable app 9 | main-is: Main.hs 10 | hs-source-dirs: src/main/haskell 11 | build-depends: base 12 | , lib1 13 | , lib2 14 | default-language: Haskell2010 15 | 16 | test-suite app-test 17 | type: exitcode-stdio-1.0 18 | build-depends: base 19 | hs-source-dirs: src/test/haskell 20 | main-is: Test.hs 21 | default-language: Haskell2010 22 | -------------------------------------------------------------------------------- /src/test-projects/test1-pride/app/build.gradle: -------------------------------------------------------------------------------- 1 | group = 'com.prezi.haskell.gradle.systests' 2 | version = '1.0' 3 | 4 | buildscript { 5 | repositories { 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath fileTree(dir: "$pluginBuildDir/integ-test-bundle/lib", include: '*.jar') 10 | classpath "com.prezi.pride:gradle-pride-plugin:0.10" 11 | } 12 | } 13 | 14 | apply plugin: "haskell" 15 | apply plugin: "pride" 16 | 17 | dynamicDependencies { 18 | main group: "com.prezi.haskell.gradle.systests", name: "lib1", configuration: "main" 19 | main group: "com.prezi.haskell.gradle.systests", name: "lib2", configuration: "main" 20 | } 21 | 22 | haskell.isExecutable = true 23 | 24 | -------------------------------------------------------------------------------- /src/test-projects/test1-pride/app/src/main/haskell/Main.hs: -------------------------------------------------------------------------------- 1 | import Lib1 2 | import Lib2 3 | 4 | main :: IO () 5 | main = do 6 | printGreeting $ hello "world" 7 | -------------------------------------------------------------------------------- /src/test-projects/test1-pride/app/src/test/haskell/Test.hs: -------------------------------------------------------------------------------- 1 | 2 | main :: IO () 3 | main = putStrLn "test" 4 | -------------------------------------------------------------------------------- /src/test-projects/test1-pride/lib1/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /src/test-projects/test1-pride/lib1/build.gradle: -------------------------------------------------------------------------------- 1 | group = 'com.prezi.haskell.gradle.systests' 2 | version = '1.0' 3 | 4 | buildscript { 5 | repositories { 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath fileTree(dir: "$pluginBuildDir/integ-test-bundle/lib", include: '*.jar') 10 | classpath "com.prezi.pride:gradle-pride-plugin:0.10" 11 | } 12 | } 13 | 14 | apply plugin: "haskell" 15 | apply plugin: "pride" 16 | -------------------------------------------------------------------------------- /src/test-projects/test1-pride/lib1/lib1.cabal: -------------------------------------------------------------------------------- 1 | name: lib1 2 | version: 0.1.0.0 3 | author: Daniel Vigovszky 4 | maintainer: daniel.vigovszky@gmail.com 5 | build-type: Simple 6 | cabal-version: >=1.10 7 | 8 | library 9 | hs-source-dirs: src/main/haskell 10 | exposed-modules: Lib1 11 | build-depends: base 12 | default-language: Haskell2010 13 | -------------------------------------------------------------------------------- /src/test-projects/test1-pride/lib1/src/main/haskell/Lib1.hs: -------------------------------------------------------------------------------- 1 | module Lib1 where 2 | 3 | hello :: String -> String 4 | hello name = "hello " ++ name 5 | -------------------------------------------------------------------------------- /src/test-projects/test1-pride/lib2/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /src/test-projects/test1-pride/lib2/build.gradle: -------------------------------------------------------------------------------- 1 | group = 'com.prezi.haskell.gradle.systests' 2 | version = '1.0' 3 | 4 | buildscript { 5 | repositories { 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath fileTree(dir: "$pluginBuildDir/integ-test-bundle/lib", include: '*.jar') 10 | classpath "com.prezi.pride:gradle-pride-plugin:0.10" 11 | } 12 | } 13 | 14 | apply plugin: "haskell" 15 | apply plugin: "pride" 16 | 17 | dynamicDependencies { 18 | main group: "com.prezi.haskell.gradle.systests", name: "lib1", version: "1.0", configuration: "main" 19 | } 20 | -------------------------------------------------------------------------------- /src/test-projects/test1-pride/lib2/lib2.cabal: -------------------------------------------------------------------------------- 1 | name: lib2 2 | version: 0.3 3 | author: Daniel Vigovszky 4 | maintainer: daniel.vigovszky@gmail.com 5 | build-type: Simple 6 | cabal-version: >=1.10 7 | 8 | library 9 | hs-source-dirs: src/main/haskell 10 | exposed-modules: Lib2 11 | build-depends: base 12 | , ansi-wl-pprint 13 | , lib1 14 | default-language: Haskell2010 15 | -------------------------------------------------------------------------------- /src/test-projects/test1-pride/lib2/src/main/haskell/Lib2.hs: -------------------------------------------------------------------------------- 1 | module Lib2 where 2 | 3 | import Text.PrettyPrint.ANSI.Leijen 4 | 5 | printGreeting :: String -> IO () 6 | printGreeting greeting = putDoc $ text greeting <> linebreak 7 | -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/app/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/app/app.cabal: -------------------------------------------------------------------------------- 1 | name: app 2 | version: 1.2.3.4 3 | author: Daniel Vigovszky 4 | maintainer: daniel.vigovszky@gmail.com 5 | build-type: Simple 6 | cabal-version: >=1.10 7 | 8 | executable app 9 | main-is: Main.hs 10 | hs-source-dirs: src/main/haskell 11 | build-depends: base 12 | , text 13 | , lib1 14 | , lib2 15 | default-language: Haskell2010 16 | 17 | test-suite app-test 18 | type: exitcode-stdio-1.0 19 | build-depends: base 20 | hs-source-dirs: src/test/haskell 21 | main-is: Test.hs 22 | default-language: Haskell2010 23 | -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/app/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | main project(path: ":lib1", configuration: "main") 4 | main project(path: ":lib2", configuration: "main") 5 | } 6 | 7 | haskell { 8 | isExecutable = true 9 | packageFlags["text"] = ["integer-simple": "false"] 10 | } 11 | -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/app/src/main/haskell/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | import Data.Text -- Using Text to test the integer-simple flag handling with stack 4 | import Lib1 5 | import Lib2 6 | 7 | main :: IO () 8 | main = do 9 | printGreeting $ hello $ unpack "world" 10 | -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/app/src/test/haskell/Test.hs: -------------------------------------------------------------------------------- 1 | 2 | main :: IO () 3 | main = putStrLn "test" 4 | -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/build.gradle: -------------------------------------------------------------------------------- 1 | pluginBuildDir = getProperty("pluginBuildDir") 2 | 3 | buildscript { 4 | dependencies { 5 | classpath fileTree(dir: "$pluginBuildDir/integ-test-bundle/lib", include: '*.jar') 6 | } 7 | } 8 | 9 | allprojects { 10 | apply plugin: "haskell" 11 | } -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/lib1/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/lib1/build.gradle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prezi/gradle-haskell-plugin/a1332e88b9d7aa46173b790fa48beb9a5c3a2cf6/src/test-projects/test1-with-text/lib1/build.gradle -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/lib1/lib1.cabal: -------------------------------------------------------------------------------- 1 | name: lib1 2 | version: 0.1.0.0 3 | author: Daniel Vigovszky 4 | maintainer: daniel.vigovszky@gmail.com 5 | build-type: Simple 6 | cabal-version: >=1.10 7 | 8 | library 9 | hs-source-dirs: src/main/haskell 10 | exposed-modules: Lib1 11 | build-depends: base 12 | default-language: Haskell2010 13 | -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/lib1/src/main/haskell/Lib1.hs: -------------------------------------------------------------------------------- 1 | module Lib1 where 2 | 3 | hello :: String -> String 4 | hello name = "hello " ++ name 5 | -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/lib2/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/lib2/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | main project(path: ":lib1", configuration: "main") 4 | } -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/lib2/lib2.cabal: -------------------------------------------------------------------------------- 1 | name: lib2 2 | version: 0.3 3 | author: Daniel Vigovszky 4 | maintainer: daniel.vigovszky@gmail.com 5 | build-type: Simple 6 | cabal-version: >=1.10 7 | 8 | library 9 | hs-source-dirs: src/main/haskell 10 | exposed-modules: Lib2 11 | build-depends: base 12 | , ansi-wl-pprint 13 | , lib1 14 | default-language: Haskell2010 15 | -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/lib2/src/main/haskell/Lib2.hs: -------------------------------------------------------------------------------- 1 | module Lib2 where 2 | 3 | import Text.PrettyPrint.ANSI.Leijen 4 | 5 | printGreeting :: String -> IO () 6 | printGreeting greeting = putDoc $ text greeting <> linebreak 7 | -------------------------------------------------------------------------------- /src/test-projects/test1-with-text/settings.gradle: -------------------------------------------------------------------------------- 1 | include 'app' 2 | include 'lib1' 3 | include 'lib2' -------------------------------------------------------------------------------- /src/test-projects/test1/app/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /src/test-projects/test1/app/app.cabal: -------------------------------------------------------------------------------- 1 | name: app 2 | version: 1.2.3.4 3 | author: Daniel Vigovszky 4 | maintainer: daniel.vigovszky@gmail.com 5 | build-type: Simple 6 | cabal-version: >=1.10 7 | 8 | executable app 9 | main-is: Main.hs 10 | hs-source-dirs: src/main/haskell 11 | build-depends: base 12 | , lib1 13 | , lib2 14 | default-language: Haskell2010 15 | 16 | test-suite app-test 17 | type: exitcode-stdio-1.0 18 | build-depends: base 19 | hs-source-dirs: src/test/haskell 20 | main-is: Test.hs 21 | default-language: Haskell2010 22 | -------------------------------------------------------------------------------- /src/test-projects/test1/app/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | main project(path: ":lib1", configuration: "main") 4 | main project(path: ":lib2", configuration: "main") 5 | } 6 | 7 | haskell.isExecutable = true 8 | 9 | -------------------------------------------------------------------------------- /src/test-projects/test1/app/src/main/haskell/Main.hs: -------------------------------------------------------------------------------- 1 | import Lib1 2 | import Lib2 3 | 4 | main :: IO () 5 | main = do 6 | printGreeting $ hello "world" 7 | -------------------------------------------------------------------------------- /src/test-projects/test1/app/src/test/haskell/Test.hs: -------------------------------------------------------------------------------- 1 | 2 | main :: IO () 3 | main = putStrLn "test" 4 | -------------------------------------------------------------------------------- /src/test-projects/test1/build.gradle: -------------------------------------------------------------------------------- 1 | pluginBuildDir = getProperty("pluginBuildDir") 2 | 3 | buildscript { 4 | dependencies { 5 | classpath fileTree(dir: "$pluginBuildDir/integ-test-bundle/lib", include: '*.jar') 6 | } 7 | } 8 | 9 | allprojects { 10 | apply plugin: "haskell" 11 | } -------------------------------------------------------------------------------- /src/test-projects/test1/lib1/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /src/test-projects/test1/lib1/build.gradle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prezi/gradle-haskell-plugin/a1332e88b9d7aa46173b790fa48beb9a5c3a2cf6/src/test-projects/test1/lib1/build.gradle -------------------------------------------------------------------------------- /src/test-projects/test1/lib1/lib1.cabal: -------------------------------------------------------------------------------- 1 | name: lib1 2 | version: 0.1.0.0 3 | author: Daniel Vigovszky 4 | maintainer: daniel.vigovszky@gmail.com 5 | build-type: Simple 6 | cabal-version: >=1.10 7 | 8 | library 9 | hs-source-dirs: src/main/haskell 10 | exposed-modules: Lib1 11 | build-depends: base 12 | default-language: Haskell2010 13 | -------------------------------------------------------------------------------- /src/test-projects/test1/lib1/src/main/haskell/Lib1.hs: -------------------------------------------------------------------------------- 1 | module Lib1 where 2 | 3 | hello :: String -> String 4 | hello name = "hello " ++ name 5 | -------------------------------------------------------------------------------- /src/test-projects/test1/lib2/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /src/test-projects/test1/lib2/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | main project(path: ":lib1", configuration: "main") 4 | } -------------------------------------------------------------------------------- /src/test-projects/test1/lib2/lib2.cabal: -------------------------------------------------------------------------------- 1 | name: lib2 2 | version: 0.3 3 | author: Daniel Vigovszky 4 | maintainer: daniel.vigovszky@gmail.com 5 | build-type: Simple 6 | cabal-version: >=1.10 7 | 8 | library 9 | hs-source-dirs: src/main/haskell 10 | exposed-modules: Lib2 11 | build-depends: base 12 | , ansi-wl-pprint 13 | , lib1 14 | default-language: Haskell2010 15 | -------------------------------------------------------------------------------- /src/test-projects/test1/lib2/src/main/haskell/Lib2.hs: -------------------------------------------------------------------------------- 1 | module Lib2 where 2 | 3 | import Text.PrettyPrint.ANSI.Leijen 4 | 5 | printGreeting :: String -> IO () 6 | printGreeting greeting = putDoc $ text greeting <> linebreak 7 | -------------------------------------------------------------------------------- /src/test-projects/test1/settings.gradle: -------------------------------------------------------------------------------- 1 | include 'app' 2 | include 'lib1' 3 | include 'lib2' -------------------------------------------------------------------------------- /src/test-projects/test2/app/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /src/test-projects/test2/app/app.cabal: -------------------------------------------------------------------------------- 1 | name: app 2 | version: 1.2.3.4 3 | author: Daniel Vigovszky 4 | maintainer: daniel.vigovszky@gmail.com 5 | build-type: Simple 6 | cabal-version: >=1.10 7 | 8 | executable app 9 | main-is: Main.hs 10 | hs-source-dirs: src/main/haskell 11 | build-depends: base 12 | , lib1 13 | , lib2 14 | default-language: Haskell2010 15 | 16 | test-suite app-test 17 | type: exitcode-stdio-1.0 18 | build-depends: base 19 | hs-source-dirs: src/test/haskell 20 | main-is: Test.hs 21 | default-language: Haskell2010 22 | -------------------------------------------------------------------------------- /src/test-projects/test2/app/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | main project(path: ":lib2", configuration: "main") 4 | } 5 | 6 | haskell.isExecutable = true 7 | 8 | -------------------------------------------------------------------------------- /src/test-projects/test2/app/src/main/haskell/Main.hs: -------------------------------------------------------------------------------- 1 | import Lib2 2 | 3 | main :: IO () 4 | main = do 5 | printGreeting $ "world" 6 | -------------------------------------------------------------------------------- /src/test-projects/test2/app/src/test/haskell/Test.hs: -------------------------------------------------------------------------------- 1 | 2 | main :: IO () 3 | main = putStrLn "test" 4 | -------------------------------------------------------------------------------- /src/test-projects/test2/build.gradle: -------------------------------------------------------------------------------- 1 | pluginBuildDir = getProperty("pluginBuildDir") 2 | 3 | buildscript { 4 | dependencies { 5 | classpath fileTree(dir: "$pluginBuildDir/integ-test-bundle/lib", include: '*.jar') 6 | } 7 | } 8 | 9 | allprojects { 10 | apply plugin: "haskell" 11 | } -------------------------------------------------------------------------------- /src/test-projects/test2/lib1/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /src/test-projects/test2/lib1/build.gradle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prezi/gradle-haskell-plugin/a1332e88b9d7aa46173b790fa48beb9a5c3a2cf6/src/test-projects/test2/lib1/build.gradle -------------------------------------------------------------------------------- /src/test-projects/test2/lib1/lib1.cabal: -------------------------------------------------------------------------------- 1 | name: lib1 2 | version: 0.1.0.0 3 | author: Daniel Vigovszky 4 | maintainer: daniel.vigovszky@gmail.com 5 | build-type: Simple 6 | cabal-version: >=1.10 7 | 8 | library 9 | hs-source-dirs: src/main/haskell 10 | exposed-modules: Lib1 11 | build-depends: base 12 | default-language: Haskell2010 13 | -------------------------------------------------------------------------------- /src/test-projects/test2/lib1/src/main/haskell/Lib1.hs: -------------------------------------------------------------------------------- 1 | module Lib1 where 2 | 3 | hello :: String -> String 4 | hello name = "hello " ++ name 5 | -------------------------------------------------------------------------------- /src/test-projects/test2/lib2/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /src/test-projects/test2/lib2/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | main project(path: ":lib1", configuration: "main") 4 | } -------------------------------------------------------------------------------- /src/test-projects/test2/lib2/lib2.cabal: -------------------------------------------------------------------------------- 1 | name: lib2 2 | version: 0.3 3 | author: Daniel Vigovszky 4 | maintainer: daniel.vigovszky@gmail.com 5 | build-type: Simple 6 | cabal-version: >=1.10 7 | 8 | library 9 | hs-source-dirs: src/main/haskell 10 | exposed-modules: Lib2 11 | build-depends: base 12 | , ansi-wl-pprint 13 | , lib1 14 | default-language: Haskell2010 15 | -------------------------------------------------------------------------------- /src/test-projects/test2/lib2/src/main/haskell/Lib2.hs: -------------------------------------------------------------------------------- 1 | module Lib2 where 2 | 3 | import Lib1 4 | import Text.PrettyPrint.ANSI.Leijen 5 | 6 | printGreeting :: String -> IO () 7 | printGreeting greeting = putDoc $ text (hello greeting) <> linebreak 8 | -------------------------------------------------------------------------------- /src/test-projects/test2/settings.gradle: -------------------------------------------------------------------------------- 1 | include 'app' 2 | include 'lib1' 3 | include 'lib2' -------------------------------------------------------------------------------- /src/test/scala/com/prezi/haskell/gradle/unittests/ApiHelperSpecs.scala: -------------------------------------------------------------------------------- 1 | package com.prezi.haskell.gradle.unittests 2 | 3 | import java.io.File 4 | 5 | import com.prezi.haskell.gradle.ApiHelper 6 | import org.gradle.internal.reflect.Instantiator 7 | import org.junit.runner.RunWith 8 | import org.specs2.mock.Mockito 9 | import org.specs2.mutable.SpecificationWithJUnit 10 | import org.specs2.runner.JUnitRunner 11 | 12 | @RunWith(classOf[JUnitRunner]) 13 | class ApiHelperSpecs extends SpecificationWithJUnit with Mockito { 14 | 15 | "asAction" should { 16 | "create an equivalent action" in { 17 | val input: String = "a" 18 | var target: String = "" 19 | val fn = (input: String) => target = input + input 20 | 21 | val action = ApiHelper.asAction[String](fn) 22 | action.execute(input) 23 | 24 | target mustEqual "aa" 25 | } 26 | } 27 | 28 | "asClosure" should { 29 | "create an equivalent closure" in { 30 | val input: String = "b" 31 | var target: String = "" 32 | val fn = (input: String) => target = input + input 33 | 34 | val closure = ApiHelper.asClosure[String](fn) 35 | closure.call(input) 36 | 37 | target mustEqual "bb" 38 | } 39 | } 40 | 41 | "asClosureWithReturn" should { 42 | "create an equivalent closure" in { 43 | val input: String = "b" 44 | val fn = (input: String) => input + input 45 | 46 | val closure = ApiHelper.asClosureWithReturn[String, String](fn) 47 | 48 | closure.call(input) mustEqual "bb" 49 | } 50 | } 51 | 52 | "instantiatorExt" should { 53 | "call instantiator's newInstance method" in { 54 | val instantiator = mock[Instantiator] 55 | 56 | case class TestClass() 57 | import ApiHelper._ 58 | 59 | instantiator.create[TestClass]("a", 11 : java.lang.Integer) 60 | 61 | there was one(instantiator).newInstance(classOf[TestClass], "a", 11 : java.lang.Integer) 62 | } 63 | } 64 | 65 | "fileExt" should { 66 | import ApiHelper._ 67 | 68 | "be able to combine paths" in { 69 | new File("root") "child" mustEqual new File("root/child") 70 | } 71 | 72 | "combining with empty string is id" in { 73 | new File("root") "" mustEqual new File("root") 74 | } 75 | } 76 | } 77 | --------------------------------------------------------------------------------