/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.maven; import com.google.common.collect.Lists; import com.google.common.io.Files; import gw.config.AbstractPlatformHelper; import gw.config.CommonServices; import gw.config.IPlatformHelper; import gw.config.Registry; import gw.fs.IDirectory; import gw.lang.GosuShop; import gw.lang.init.GosuInitialization; import gw.lang.reflect.IType; import gw.lang.reflect.ITypeLoader; import gw.lang.reflect.ITypeSystem; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.gs.GosuClassPathThing; import gw.lang.reflect.gs.GosuClassTypeLoader; import gw.lang.reflect.gs.IGosuClass; import gw.lang.reflect.module.Dependency; import gw.lang.reflect.module.IExecutionEnvironment; import gw.lang.reflect.module.IModule; import gw.lang.reflect.module.IProject; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.util.Collections; import java.util.List; import java.util.Set; /** */ @SuppressWarnings("unused") public abstract class AbstractCompileMojo extends AbstractMojo { @Component protected MavenProject mavenProject; @Parameter(property = "gosu.compile.skip") protected boolean skip; @Parameter protected List<String> packages; @Parameter protected List<String> exclusions = Collections.emptyList(); @Parameter protected List<File> roots; /** * Name of the class (which should implement Runnable) to call before setting up Gosu. */ @Parameter protected String setupClass; /** * Module to compile. */ @Parameter(defaultValue = "${project.artifactId}") protected String moduleName; /** * Ignore all compilation errors. */ @Parameter(defaultValue = "false") protected boolean ignoreErrors; // We need really global lock due to the URL#handlers being JVM-wide global. private static Object LOCK = "reallygloballock"; @Override public void execute() throws MojoExecutionException, MojoFailureException { if (skip) { getLog().info("Skipping Gosu compiler plugin"); return; } // Sure, we are thread safe. synchronized(LOCK) { // FIXME-isd: instead of resetting gosu protocol handler, we would rather like to have total control over // Gosu classloading. GosuClassPathThing.cleanup(); setupAndCompile(); } } private void setupAndCompile() throws MojoFailureException { GosuInitialization init = null; try { IProject project = new MavenBackedProject(mavenProject); IExecutionEnvironment env = TypeSystem.getExecutionEnvironment(project); init = setupGosu(env); IModule module = env.getModule(moduleName); TypeSystem.pushModule(module); try { compileGosu(); } finally { TypeSystem.popModule(module); } } catch (Exception e) { e.printStackTrace(); throw new MojoFailureException("Failed to compile gosu classes", e); } finally { if (init != null) { init.uninitializeMultipleModules(); } } } protected abstract File getOutputFolder(); protected abstract List<File> getSources(); protected abstract List<File> getClassPath(); protected List<File> getJreClassPath() { return Collections.emptyList(); } protected List<File> getRoots() { return roots == null ? Collections.singletonList(mavenProject.getBasedir()) : roots; } private GosuInitialization setupGosu(IExecutionEnvironment env) throws URISyntaxException, IOException, MojoExecutionException { // Run setup runCustomSetup(); GosuInitialization init = GosuInitialization.instance(env); // Always override platform helper CommonServices.getKernel().redefineService_Privileged(IPlatformHelper.class, new MavenPlatformHelper()); IModule jre = createJreModule(env); IModule current = createMasterModule(env); IModule global = createGlobalModule(env, current, jre); // Setup dependencies current.addDependency(new Dependency(jre, true)); global.addDependency(new Dependency(current, true)); global.addDependency(new Dependency(jre, true)); init.initializeMultipleModules(Lists.newArrayList(jre, current, global)); return init; } protected void runCustomSetup() throws MojoExecutionException { // Run setup class if (setupClass != null) { ClassLoader cl = createSetupClassLoader(); try { Class<?> setup = cl.loadClass(setupClass); Runnable runnable = (Runnable) setup.getConstructor(String.class).newInstance(moduleName); runnable.run(); } catch (Exception e) { throw new MojoExecutionException("Cannot run custom setup step!", e); } } else { // Reset CommonServices ITypeSystem ts = CommonServices.getTypeSystem(); Registry.initDefaults(); // It has all our shutdown listeners! We need it back! CommonServices.sneakySetTypeSystem(ts); } } /** * Create a classloader to load setup class from. */ protected ClassLoader createSetupClassLoader() { List<URL> urls = Lists.newArrayList(); urls.addAll(Lists.transform(getDependencies(), ToURL.INSTANCE)); urls.addAll(Lists.transform(getJreClassPath(), ToURL.INSTANCE)); urls.addAll(Lists.transform(getClassPath(), ToURL.INSTANCE)); return new URLClassLoader(urls.toArray(new URL[urls.size()]), getClass().getClassLoader()); } protected void compileGosu() throws IOException { // FIXME-isd: Iterate through gosu classes in the source directories only... ITypeLoader typeLoader = TypeSystem.getTypeLoader(GosuClassTypeLoader.class, TypeSystem.getCurrentModule()); Set<? extends CharSequence> allTypeNames = typeLoader.getAllTypeNames(); int count = 0; for (CharSequence cs : allTypeNames) { String typeName = cs.toString(); if (includeType(typeName)) { IType type = TypeSystem.getByFullName(typeName); if (type instanceof IGosuClass) { IGosuClass gosuClass = (IGosuClass) type; // Write class + inner classes String fileName = type.getName().replace('.', '/'); count += compileClass(gosuClass, fileName); } } } getLog().info("Compiled " + count + " Gosu types to the " + getOutputFolder()); } private int compileClass(IGosuClass gosuClass, String fileName) throws IOException { writeClassToDisk(gosuClass, fileName); int count = 1; for (IGosuClass innerClass : gosuClass.getInnerClasses()) { compileClass(innerClass, fileName + '$' + innerClass.getRelativeName()); count++; } return count; } private IModule createJreModule(IExecutionEnvironment env) throws URISyntaxException, IOException { IModule jreModule = env.createJreModule(); List<File> cp = Lists.newArrayList(); // Let's get all JARs from the bootstrap classloader. URLClassLoader bootstrap = (URLClassLoader) ClassLoader.getSystemClassLoader().getParent(); for (URL url : bootstrap.getURLs()) { File file = new File(url.toURI()); cp.add(file); } // Wee need rt.jar, too URL url = bootstrap.getResource("java/lang/Object.class"); if (url != null) { final JarURLConnection connection = (JarURLConnection) url.openConnection(); File rtJar = new File(connection.getJarFileURL().toURI()); cp.add(rtJar); } // Add dependencies classpath cp.addAll(getDependencies()); cp.addAll(getJreClassPath()); jreModule.configurePaths(Lists.transform(cp, ToDirectory.INSTANCE), Collections.<IDirectory>emptyList()); // FIXME-isd: we put all Java classes in JRE module, to support PL initialization. List<IDirectory> cp2 = Lists.newArrayList(jreModule.getJavaClassPath()); cp2.addAll(Lists.transform(getClassPath(), ToDirectory.INSTANCE)); jreModule.setJavaClassPath(cp2); return jreModule; } protected List<File> getDependencies() { List<File> cp = Lists.newArrayList(); for (Artifact art : mavenProject.getArtifacts()) { if (art.getArtifactHandler().isAddedToClasspath() && art.getFile() != null) { cp.add(art.getFile()); } } return cp; } private IModule createMasterModule(IExecutionEnvironment env) { IModule module = GosuShop.createModule(env, mavenProject.getArtifactId()); // Roots List<IDirectory> roots = Lists.newArrayList(); for (File root : getRoots()) { roots.add(CommonServices.getFileSystem().getIDirectory(root)); } module.setRoots(roots); // Sources and classes // FIXME: should scan for classes in dependencies?... List<IDirectory> sources = Lists.transform(getSources(), ToDirectory.INSTANCE); module.setSourcePath(sources); // FIXME: See JRE module setup. //List<String> classpath = getClassPath(); //module.setJavaClassPath(classpath); return module; } private IModule createGlobalModule(IExecutionEnvironment env, IModule... modules) { // Collect all source paths List<IDirectory> sources = Lists.newArrayList(); for (IModule module : modules) { sources.addAll(module.getSourcePath()); } IModule globalModule = GosuShop.createGlobalModule(env); globalModule.configurePaths(Collections.<IDirectory>emptyList(), sources); return globalModule; } private void writeClassToDisk(IGosuClass type, String fileName) throws IOException { File outputFile = new File(getOutputFolder(), fileName + ".class"); byte[] bytes = null; if (ignoreErrors) { try { bytes = type.compile(); } catch (Exception e) { getLog().warn("Failed to compile type '" + type.getName() + "', ignoring."); getLog().debug("Compilation errors are ", e); } } else { bytes = type.compile(); } if (bytes != null) { outputFile.getParentFile().mkdirs(); Files.write(bytes, outputFile); } } private boolean includeType(String typeName) { // FIXME-isd: make this configurable? if (typeName.contains("Errant")) { return false; } for (String exclusion : exclusions) { if (typeName.startsWith(exclusion)) { return false; } } return true; } private static class MavenPlatformHelper extends AbstractPlatformHelper { @Override public boolean isInIDE() { // XXX: Seems like in multiple modules mode, this has to be 'true' return true; } @Override public boolean shouldCacheTypeNames() { return false; } @Override public void refresh(IModule module) { } } }