Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

432 lines (371 sloc) 15.932 kB
/*
* Copyright 2011 SpringSource
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.grails.compiler
import grails.util.BuildSettings
import grails.util.GrailsNameUtils
import grails.util.PluginBuildSettings
import org.codehaus.groovy.control.CompilationUnit
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.grails.plugins.GrailsPluginInfo
import org.springframework.core.io.Resource
import org.codehaus.groovy.grails.compiler.support.GrailsResourceLoader
import org.codehaus.groovy.grails.compiler.support.GrailsResourceLoaderHolder
/**
*
* Encapsulates the compilation logic required for a Grails application
*
* @author Graeme Rocher
* @since 1.4
*/
class GrailsProjectCompiler {
public static final List<String> EXCLUDED_PATHS = ["views", "i18n", "conf"]
private static final String CLASSPATH_REF = "grails.compile.classpath"
private static final List<String> PLUGIN_EXCLUDE_PATHS = ["views", "i18n"] // conf gets special handling
private AntBuilder ant
private File pluginDescriptor
private CompilerConfiguration config
private ClassLoader classLoader
boolean verbose = false
boolean isPluginProject = false
List<String> srcDirectories
Map javaOptions = [classpathref:CLASSPATH_REF, encoding:"UTF-8", debug:"yes"]
String basedir
String srcdir
String encoding = "UTF-8"
File targetClassesDir
File targetPluginClassesDir
def commonClasspath
def compileClasspath
def testClasspath
def runtimeClasspath
BuildSettings buildSettings
PluginBuildSettings pluginSettings
List<String> compilerExtensions = ['groovy', 'java']
/**
* Constructs a new GrailsProjectCompiler instance for the given PluginBuildSettings and optional classloader
*
* @param pluginBuildSettings The PluginBuildSettings
* @param rootLoader The ClassLoader
*/
GrailsProjectCompiler(PluginBuildSettings pluginBuildSettings, ClassLoader rootLoader = Thread.currentThread().getContextClassLoader()) {
pluginSettings = pluginBuildSettings
buildSettings = pluginBuildSettings.buildSettings
this.targetClassesDir = buildSettings.classesDir
this.targetPluginClassesDir = buildSettings.pluginClassesDir
this.basedir = buildSettings.baseDir.absolutePath
this.srcdir = buildSettings.sourceDir.absolutePath
this.classLoader = rootLoader
this.pluginDescriptor = new File(basedir).listFiles().find { it.name.endsWith("GrailsPlugin.groovy") }
this.config = config
isPluginProject = pluginDescriptor != null
srcDirectories = [ "${basedir}/grails-app/conf".toString(),
"${basedir}/grails-app/conf/spring".toString(),
"${srcdir}/groovy".toString(),
"${srcdir}/java".toString()]
def excludedPaths = EXCLUDED_PATHS
final grailsAppDirs = new File("${basedir}/grails-app").listFiles()
if (grailsAppDirs != null) {
for (dir in grailsAppDirs) {
if (dir != null) {
if (!excludedPaths?.contains(dir.name) && dir.isDirectory()) {
srcDirectories << "${dir}".toString()
}
}
}
}
initializeAntClasspaths()
Resource[] resources = pluginBuildSettings.getArtefactResources()
GrailsResourceLoader resourceLoader = new GrailsResourceLoader(resources)
GrailsResourceLoaderHolder.setResourceLoader(resourceLoader)
}
AntBuilder getAnt() {
if (ant == null) {
ant = new AntBuilder()
ant.taskdef (name: 'groovyc', classname : 'org.codehaus.groovy.grails.compiler.Grailsc')
ant.path(id: "grails.compile.classpath", compileClasspath)
}
return ant
}
void setAnt(AntBuilder ant) {
this.ant = ant
}
/**
* Configures the Grails classpath, should be called prior to any call to {@link #compile(Object) }
*/
void configureClasspath() {
ant.path(id: "grails.compile.classpath", compileClasspath)
ant.path(id: "grails.test.classpath", testClasspath)
ant.path(id: "grails.runtime.classpath", runtimeClasspath)
def grailsDir = new File("${basedir}/grails-app").listFiles()
StringBuilder cpath = new StringBuilder()
def jarFiles = getJarFiles()
for (dir in grailsDir) {
cpath << dir.absolutePath << File.pathSeparator
// Adding the grails-app folders to the root loader causes re-load issues as
// root loader returns old class before the grails GCL attempts to recompile it
// rootLoader?.addURL(dir.URL)
}
cpath << targetClassesDir.absolutePath << File.pathSeparator
cpath << targetPluginClassesDir.absolutePath << File.pathSeparator
cpath << "${basedir}/web-app/WEB-INF" << File.pathSeparator
for (File jar in jarFiles) {
cpath << jar.absolutePath << File.pathSeparator
}
// We need to set up this configuration so that we can compile the
// plugin descriptors, which lurk in the root of the plugin's project directory.
config = new CompilerConfiguration()
config.setClasspath(cpath.toString())
config.sourceEncoding = "UTF-8"
// The resources directory must be created before it is added to
// the root loader, otherwise it is quietly ignored. In other words,
// if the directory is created after its path has been added to the
// root loader, it will not be included in the classpath.
def resourcesDir = new File(buildSettings.resourcesDir.path)
if (!resourcesDir.exists()) {
resourcesDir.mkdirs()
}
classLoader?.addURL(resourcesDir.toURI().toURL())
}
/**
* Obtains all JAR files for the project that aren't declared via BuildConfig
*
* @return A list of JAR files
*/
List<File> getJarFiles() {
final libDirPath = "${basedir}/lib"
def jarFiles = listJarFiles(libDirPath)
def pluginJars = pluginSettings.pluginJarFiles
for (pluginJar in pluginJars) {
boolean matches = jarFiles.any {it.name == pluginJar.file.name}
if (!matches) jarFiles.add(pluginJar.file)
}
def userJars = listJarFiles("${buildSettings.userHome}/.grails/lib")
for (userJar in userJars) {
jarFiles.add(userJar)
}
jarFiles.addAll(getExtraDependencies())
jarFiles
}
private List<File> listJarFiles(String libDirPath) {
return new File(libDirPath).listFiles({ File f -> f.name.endsWith(".jar")} as FileFilter)?.toList() ?: []
}
/**
* Extra dependencies defined by the 'grails.compiler.dependencies' config option in BuildConfig
*
* @return
*/
List<File> getExtraDependencies() {
def jarFiles =[]
final buildConfig = buildSettings.config
if (buildConfig?.grails?.compiler?.dependencies) {
def extraDeps = ant.fileScanner(buildConfig.grails.compiler.dependencies)
for (jar in extraDeps) {
jarFiles << jar
}
}
jarFiles
}
/**
* Compiles plugin and normal sources
*/
void compileAll() {
compilePlugins()
compile()
}
/**
* Compiles project sources using the target directory passed by PluginBuildSettings
*
*/
void compile() {
compile(targetClassesDir)
}
/**
* Compiles project sources to the given target directory
*
* @param targetDir The target directory to compile to
*/
void compile(targetDir) {
def compilerPaths = { String classpathId ->
for (srcPath in srcDirectories) {
if (new File(srcPath).exists()) {
src(path:srcPath)
}
}
javac(javaOptions)
}
def classesDirPath = new File(targetDir.toString())
ant.mkdir(dir:classesDirPath)
String classpathId = "grails.compile.classpath"
ant.groovyc(destdir:classesDirPath,
classpathref:classpathId,
encoding:encoding,
verbose: verbose,
listfiles: verbose,
compilerPaths.curry(classpathId))
// If this is a plugin project, the descriptor is not included
// in the compiler's source path. So, we manually compile it now.
if (isPluginProject) {
compilePluginDescriptor(pluginDescriptor, classesDirPath)
}
}
void compilePlugins() {
compilePlugins(targetPluginClassesDir)
}
/**
* Compiles plugin sources files to the given target directory
*
* @param targetDir The target directory to compile to
*/
void compilePlugins(targetDir) {
def classesDirPath = targetDir
ant.mkdir(dir:classesDirPath)
// First compile the plugins so that we can exclude any
// classes that might conflict with the project's.
def classpathId = CLASSPATH_REF
def excludedPaths = PLUGIN_EXCLUDE_PATHS // conf gets special handling
def pluginResources = pluginSettings.pluginSourceFiles?.findAll {
!excludedPaths.contains(it.file.name) && it.file.isDirectory()
}
if (pluginResources) {
// Only perform the compilation if there are some plugins
// installed or otherwise referenced.
ant.groovyc(destdir:classesDirPath,
classpathref:classpathId,
encoding:encoding,
verbose: verbose,
listfiles: verbose) {
for (dir in pluginResources.file) {
src(path:"${dir}")
}
exclude(name: "**/BootStrap.groovy")
exclude(name: "**/BuildConfig.groovy")
exclude(name: "**/Config.groovy")
exclude(name: "**/*DataSource.groovy")
exclude(name: "**/UrlMappings.groovy")
exclude(name: "**/resources.groovy")
javac(javaOptions)
}
}
}
/**
* Compiles GSP pages for the given application name to the (optional) target directory
*
* @param grailsAppName The app name
* @param classesDir The optional classes dir, defaults to one provided by PluginBuildSettings in constructor
*/
void compileGroovyPages(String grailsAppName, classesDir = targetClassesDir) {
ant.taskdef (name: 'gspc', classname : 'org.codehaus.groovy.grails.web.pages.GroovyPageCompilerTask')
// compile gsps in grails-app/views directory
File gspTmpDir = new File(buildSettings.projectWorkDir, "gspcompile")
ant.gspc(destdir:classesDir,
srcdir:"${basedir}/grails-app/views",
packagename:GrailsNameUtils.getPropertyNameForLowerCaseHyphenSeparatedName(grailsAppName),
serverpath:"/WEB-INF/grails-app/views/",
classpathref:"grails.compile.classpath",
tmpdir:gspTmpDir)
// compile gsps in web-app directory
ant.gspc(destdir:classesDir,
srcdir:"${basedir}/web-app",
packagename:"${GrailsNameUtils.getPropertyNameForLowerCaseHyphenSeparatedName(grailsAppName)}_webapp",
serverpath:"/",
classpathref:"grails.compile.classpath",
tmpdir:gspTmpDir)
// compile views in plugins
def pluginInfos = pluginSettings.supportedPluginInfos
if (pluginInfos) {
for (GrailsPluginInfo info in pluginInfos) {
File pluginViews = new File(info.pluginDir.file, "grails-app/views")
if (pluginViews.exists()) {
def viewPrefix="/WEB-INF/plugins/${info.name}-${info.version}/grails-app/views/"
ant.gspc(destdir:classesDir,
srcdir:pluginViews,
packagename:GrailsNameUtils.getPropertyNameForLowerCaseHyphenSeparatedName(info.name),
serverpath:viewPrefix,
classpathref:"grails.compile.classpath",
tmpdir:gspTmpDir)
}
}
}
}
/**
* Compiles a given plugin descriptor file - *GrailsPlugin.groovy.
*/
protected compilePluginDescriptor(File descriptor, File classesDir) {
def className = descriptor.name - '.groovy'
def classFile = new File(classesDir, "${className}.class")
if (descriptor.lastModified() > classFile.lastModified()) {
ant.echo(message: "Compiling plugin descriptor...")
config.setTargetDirectory(classesDir)
def cl = new URLClassLoader([classesDir,targetPluginClassesDir]*.toURL() as URL[], classLoader)
def unit = new CompilationUnit(config, null, new GroovyClassLoader(cl))
unit.addSource(descriptor)
unit.compile()
}
}
private initializeAntClasspaths() {
commonClasspath = {
def grailsDir = new File("${basedir}/grails-app").listFiles()
for (File file in grailsDir) {
pathelement(location: "${file.absolutePath}")
}
def pluginLibDirs = pluginSettings.pluginLibDirectories.findAll { it.exists() }
for (pluginLib in pluginLibDirs) {
fileset(dir: pluginLib.file.absolutePath, excludes:'**/*.txt')
}
}
compileClasspath = {
commonClasspath.delegate = delegate
commonClasspath.call()
def dependencies = buildSettings.compileDependencies
if (dependencies) {
for (File f in dependencies) {
if (f) {
pathelement(location: f.absolutePath)
}
}
}
pathelement(location: "${targetPluginClassesDir.absolutePath}")
}
testClasspath = {
commonClasspath.delegate = delegate
commonClasspath.call()
def dependencies = buildSettings.testDependencies
if (dependencies) {
for (File f in dependencies) {
if (f) {
pathelement(location: f.absolutePath)
}
}
}
pathelement(location: "${targetClassesDir.absolutePath}")
pathelement(location: "${targetPluginClassesDir.absolutePath}")
}
runtimeClasspath = {
commonClasspath.delegate = delegate
commonClasspath.call()
def dependencies = buildSettings.runtimeDependencies
if (dependencies) {
for (File f in dependencies) {
if (f) {
pathelement(location: f.absolutePath)
}
}
}
pathelement(location: "${targetPluginClassesDir.absolutePath}")
pathelement(location: "${targetClassesDir.absolutePath}")
}
}
}
Jump to Line
Something went wrong with that request. Please try again.