|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676 |
- /* *******************************************************************
- * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC),
- * 2003 Contributors.
- * All rights reserved.
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * PARC initial implementation
- * ******************************************************************/
-
- package org.aspectj.internal.tools.build;
-
- import java.io.File;
- import java.io.FileFilter;
- import java.io.InputStream;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.Iterator;
- import java.util.List;
- import java.util.ListIterator;
- import java.util.Properties;
- import java.util.StringTokenizer;
-
- import org.apache.tools.ant.BuildException;
- import org.aspectj.internal.tools.build.Result.Kind;
-
- /**
- * Template class to build (eclipse) modules (and, weakly, products), including
- * any required modules. When building modules, this assumes:
- * <ul>
- * <li>the name of the module is the base name of the module directory</li>
- * <li>all module directories are in the same base (workspace) directory</li>
- * <li>the name of the target module jar is {moduleName}.jar</li>
- * <li>a module directory contains a <code>.classpath</code> file with
- * (currently line-parseable) entries per Eclipse (XML) conventions</li>
- * <li><code>Builder.RESOURCE_PATTERN</code> identifies all resources to copy
- * to output.</li>
- * <li>This can safely trim test-related code:
- * <ul>
- * <li>source directories named "testsrc"</li>
- * <li>libraries named "junit.jar"</li>
- * <li>required modules whose names start with "testing"</li>
- * </ul>
- * <li>A file <code>{moduleDir}/{moduleName}.properties</code> is a property
- * file possibly containing entries defining requirements to be merged with the
- * output jar (deprecated mechanism - use assembleAll or products)</li>
- * </ul>
- * This currently provides no control over the compile or assembly process, but
- * clients can harvest <code>{moduleDir}/bin</code> directories to re-use the
- * results of eclipse compiles.
- * <p>
- * When building products, this assumes:
- * <ul>
- * <li>the installer-resources directory is a peer of the products directory,
- * itself the parent of the particular product directory.</li>
- * <li>the dist, jar, product, and base (module) directory are set</li>
- * <li>the product distribution consists of all (and only) the files in the
- * dist sub-directory of the product directory</li>
- * <li>files in the dist sub-directory that are empty and end with .jar
- * represent modules to build, either as named or through aliases known here.</li>
- * <li>When assembling the distribution, all non-binary files are to be
- * filtered.
- * <li>
- * <li>the name of the product installer is
- * aspectj-{productName}-{version}.jar, where {productName} is the base name of
- * the product directory</li>
- * </ul>
- * <p>
- * When run using main(String[]), all relevant Ant libraries and properties must
- * be defined.
- * <p>
- * Written to compile standalone. Refactor if using utils, bridge, etc.
- */
- public abstract class Builder {
-
- /**
- * This has only weak forms for build instructions needed: - resource
- * pattern - compiler selection and control
- *
- * Both assumed and generated paths are scattered; see XXXNameLiteral and
- * XXXFileLiteral.
- *
- * Builder is supposed to be thread-safe, but currently caches build
- * properties to tunnel for filters. hmm.
- */
-
- public static final String RESOURCE_PATTERN;
-
- public static final String BINARY_SOURCE_PATTERN;
-
- public static final String ALL_PATTERN;
-
- /** enable copy filter semantics */
- protected static final boolean FILTER_ON = true;
-
- /** disable copy filter semantics */
- protected static final boolean FILTER_OFF = false;
-
- /** define libraries to skip as comma-delimited values for this key */
- private static final String SKIP_LIBRARIES_KEY = "skip.libraries";
-
- /** List (String) names of libraries to skip during assembly */
- private static final List<String> SKIP_LIBRARIES;
-
- private static final String ERROR_KEY = "error loading properties";
-
- private static final Properties PROPS;
- static {
- PROPS = new Properties();
- List<String> skips = Collections.emptyList();
- String resourcePattern = "**/*.txt,**/*.rsc,**/*.gif,**/*.properties";
- String allPattern = "**/*";
- String binarySourcePattern = "**/*.rsc,**/*.gif,**/*.jar,**/*.zip";
- String name = Builder.class.getName().replace('.', '/') + ".properties";
- try {
- InputStream in = Builder.class.getClassLoader()
- .getResourceAsStream(name);
- PROPS.load(in);
- allPattern = PROPS.getProperty("all.pattern");
- resourcePattern = PROPS.getProperty("resource.pattern");
- binarySourcePattern = PROPS.getProperty("binarySource.pattern");
- skips = commaStrings(PROPS.getProperty(SKIP_LIBRARIES_KEY));
- } catch (Throwable t) {
- if (t instanceof ThreadDeath) {
- throw (ThreadDeath) t;
- }
- String m = "error loading " + name + ": " + t.getClass() + " " + t;
- PROPS.setProperty(ERROR_KEY, m);
- }
- SKIP_LIBRARIES = skips;
- ALL_PATTERN = allPattern;
- BINARY_SOURCE_PATTERN = binarySourcePattern;
- RESOURCE_PATTERN = resourcePattern;
- }
-
- /**
- * Splits strings into an unmodifable <code>List</code> of String using
- * comma as the delimiter and trimming whitespace from the result.
- *
- * @param text
- * <code>String</code> to split.
- * @return unmodifiable List (String) of String delimited by comma in text
- */
- public static List commaStrings(String text) {
- if ((null == text) || (0 == text.length())) {
- return Collections.EMPTY_LIST;
- }
- List<String> strings = new ArrayList<String>();
- StringTokenizer tok = new StringTokenizer(text, ",");
- while (tok.hasMoreTokens()) {
- String token = tok.nextToken().trim();
- if (0 < token.length()) {
- strings.add(token);
- }
- }
- return Collections.unmodifiableList(strings);
- }
-
- /**
- * Map delivered-jar name to created-module name
- *
- * @param jarName
- * the String (lowercased) of the jar/zip to map
- */
- private String moduleAliasFor(String jarName) {
- String result = PROPS.getProperty("alias." + jarName, jarName);
- if (verbose && result.equals(jarName)) {
- String m = "expected alias for " + jarName;
- handler.error(m + PROPS.getProperty(ERROR_KEY, ""));
- }
- return result;
- }
-
- protected final Messager handler;
-
- protected boolean buildingEnabled;
-
- private final File tempDir;
-
- private final ArrayList tempFiles;
-
- private final boolean useEclipseCompiles;
-
- protected boolean verbose;
-
- protected Builder(File tempDir, boolean useEclipseCompiles, Messager handler) {
- Util.iaxIfNull(handler, "handler");
- this.useEclipseCompiles = useEclipseCompiles;
- this.handler = handler;
- this.tempFiles = new ArrayList();
- if ((null == tempDir) || !tempDir.canWrite() || !tempDir.isDirectory()) {
- this.tempDir = Util.makeTempDir("Builder");
- } else {
- this.tempDir = tempDir;
- }
- buildingEnabled = true;
- }
-
- /** tell builder to stop or that it's ok to run */
- public void setBuildingEnabled(boolean enabled) {
- buildingEnabled = enabled;
- }
-
- public void setVerbose(boolean verbose) {
- this.verbose = verbose;
- }
-
- private void verifyBuildSpec(BuildSpec buildSpec) {
- if (null == buildSpec.productDir) { // ensure module properties
- // derive moduleDir from baseDir + module
- if (null == buildSpec.moduleDir) {
- if (null == buildSpec.baseDir) {
- throw new BuildException("require baseDir or moduleDir");
- } else if (null == buildSpec.module) {
- throw new BuildException("require module with baseDir");
- } else {
- if (null == buildSpec.baseDir) {
- buildSpec.baseDir = new File("."); // user.home?
- }
- buildSpec.moduleDir = new File(buildSpec.baseDir,
- buildSpec.module);
- }
- } else if (null == buildSpec.baseDir) {
- // derive baseDir from moduleDir parent
- buildSpec.baseDir = buildSpec.moduleDir.getParentFile();
- // rule: base is parent
- if (null == buildSpec.baseDir) {
- buildSpec.baseDir = new File("."); // user.home?
- }
- handler.log("Builder using derived baseDir: "
- + buildSpec.baseDir);
- }
- Util.iaxIfNotCanReadDir(buildSpec.moduleDir, "moduleDir");
- if (null == buildSpec.module) {
- // derive module name from directory
- buildSpec.module = buildSpec.moduleDir.getName();
- if (null == buildSpec.module) {
- throw new BuildException("no name, even from "
- + buildSpec.moduleDir);
- }
- }
- }
- }
-
- /**
- * Find the Result (and hence Module and Modules) for this BuildSpec.
- */
- protected Result specifyResultFor(BuildSpec buildSpec) {
- if (buildSpec.trimTesting
- && (-1 != buildSpec.module.indexOf("testing"))) { // XXXNameLiteral
- String warning = "Warning - cannot trimTesting for testing modules: ";
- handler.log(warning + buildSpec.module);
- }
- Messager handler = new Messager();
- Modules modules = new Modules(buildSpec.baseDir, buildSpec.jarDir,
- handler);
-
- final Module moduleToBuild = modules.getModule(buildSpec.module);
- Kind kind = Result.kind(buildSpec.trimTesting,
- buildSpec.assembleAll);
- return moduleToBuild.getResult(kind);
- }
-
- public final boolean build(BuildSpec buildSpec) {
- if (!buildingEnabled) {
- return false;
- }
- verifyBuildSpec(buildSpec);
-
- if (null != buildSpec.productDir) {
- return buildProduct(buildSpec);
- }
- Result result = specifyResultFor(buildSpec);
- ArrayList<String> errors = new ArrayList<String>();
- try {
- return buildAll(result, errors);
- } finally {
- if (0 < errors.size()) {
- String label = "error building " + buildSpec + ": ";
- for (Iterator<String> iter = errors.iterator(); iter.hasNext();) {
- String m = label + iter.next();
- handler.error(m);
- }
- }
- }
- }
-
- /**
- * Clean up any temporary files, etc. after build completes
- */
- public boolean cleanup() {
- boolean noErr = true;
- for (ListIterator iter = tempFiles.listIterator(); iter.hasNext();) {
- File file = (File) iter.next();
- if (!Util.deleteContents(file) || !file.delete()) {
- if (noErr) {
- noErr = false;
- }
- handler.log("unable to clean up " + file);
- }
- }
- return noErr;
- }
-
- protected final boolean isLogging() {
- return (verbose && (null != this.handler));
- }
-
- protected Result[] skipUptodate(Result[] results) {
- if (null == results) {
- return new Result[0];
- }
- Result[] done = new Result[results.length];
- int to = 0;
- for (int i = 0; i < done.length; i++) {
- if ((null != results[i]) && results[i].outOfDate()) {
- done[to++] = results[i];
- }
- }
- if (to < results.length) {
- Result[] newdone = new Result[to];
- System.arraycopy(done, 0, newdone, 0, newdone.length);
- done = newdone;
- }
- return done;
- }
-
- /**
- * Build a result with all antecedants.
- *
- * @param result
- * the Result to build
- * @param errors
- * the List sink for errors, if any
- * @return false after successful build, when module jar should exist
- */
- protected final boolean buildAll(Result result, List errors) {
- Result[] buildList = skipUptodate(getAntecedantResults(result));
- ArrayList<String> doneList = new ArrayList<String>();
- if ((null != buildList) && (0 < buildList.length)) {
- if (isLogging()) {
- handler.log("modules to build: " + Arrays.asList(buildList));
- }
- for (int i = 0; i < buildList.length; i++) {
- Result required = buildList[i];
- if (!buildingEnabled) {
- return false;
- }
- String requiredName = required.getName();
- if (!doneList.contains(requiredName)) {
- doneList.add(requiredName);
- if (!buildOnly(required, errors)) {
- return false;
- }
- }
- }
- }
- return true;
- }
-
- /**
- * Build a module but no antecedants.
- *
- * @param module
- * the Module to build
- * @param errors
- * the List sink for errors, if any
- * @return false after successful build, when module jar should exist
- */
- protected final boolean buildOnly(Result result, List<String> errors) {
- if (!result.outOfDate()) {
- return true;
- }
- if (isLogging()) {
- handler.log("building " + result);
- }
- if (!buildingEnabled) {
- return false;
- }
- if (result.getKind().assemble) {
- return assembleAll(result, handler);
- }
- Module module = result.getModule();
- final File classesDir;
- if (useEclipseCompiles) {
- classesDir = new File(module.moduleDir, "bin"); // FileLiteral
- } else {
- String name = "classes-" + System.currentTimeMillis();
- classesDir = new File(tempDir, name);
- }
- if (verbose) {
- handler.log("buildOnly " + module);
- }
- try {
- return (compile(result, classesDir,useEclipseCompiles, errors))
- && assemble(result, classesDir, errors);
- } finally {
- if (!useEclipseCompiles && !Util.delete(classesDir)) {
- errors.add("buildOnly unable to delete " + classesDir);
- }
- }
- }
-
- /**
- * Register temporary file or directory to be deleted when the build is
- * complete, even if an Exception is thrown.
- */
- protected void addTempFile(File tempFile) {
- if (null != tempFile) {
- tempFiles.add(tempFile);
- }
- }
-
- /**
- * Build product by discovering any modules to build, building those,
- * assembling the product distribution, and optionally creating an installer
- * for it.
- *
- * @return true on success
- */
- protected final boolean buildProduct(BuildSpec buildSpec)
- throws BuildException {
- Util.iaxIfNull(buildSpec, "buildSpec");
-
- if (!buildSpec.trimTesting) {
- buildSpec.trimTesting = true;
- handler.log("testing trimmed for " + buildSpec);
- }
- Util.iaxIfNotCanReadDir(buildSpec.productDir, "productDir");
- Util.iaxIfNotCanReadDir(buildSpec.baseDir, "baseDir");
- Util.iaxIfNotCanWriteDir(buildSpec.distDir, "distDir");
-
- // ---- discover modules to build, and build them
- Modules modules = new Modules(buildSpec.baseDir, buildSpec.jarDir,
- handler);
- ProductModule[] productModules = discoverModules(buildSpec.productDir,
- modules);
- for (int i = 0; i < productModules.length; i++) {
- if (buildSpec.verbose) {
- handler.log("building product module " + productModules[i]);
- }
- if (!buildProductModule(productModules[i])) {
- return false;
- }
- }
- if (buildSpec.verbose) {
- handler.log("assembling product module for " + buildSpec);
- }
-
- // ---- assemble product distribution
- final String productName = buildSpec.productDir.getName();
- final File targDir = new File(buildSpec.distDir, productName);
- final String targDirPath = targDir.getPath();
- if (targDir.canWrite()) {
- Util.deleteContents(targDir);
- }
-
- if (!targDir.canWrite() && !targDir.mkdirs()) {
- if (buildSpec.verbose) {
- handler.log("buildProduct unable to create " + targDir);
- }
- return false;
- }
-
- // copy non-binaries (with filter)
- File distDir = new File(buildSpec.productDir, "dist");
- if (!copyNonBinaries(buildSpec, distDir, targDir)) {
- return false;
- }
-
- // copy binaries (but not module flag files)
- String excludes = null;
- {
- StringBuffer buf = new StringBuffer();
- for (int i = 0; i < productModules.length; i++) {
- if (0 < buf.length()) {
- buf.append(",");
- }
- buf.append(productModules[i].relativePath);
- }
- if (0 < buf.length()) {
- excludes = buf.toString();
- }
- }
-
- if (!copyBinaries(buildSpec, distDir, targDir, excludes)) {
- return false;
- }
-
- // copy binaries associated with module flag files
- for (int i = 0; i < productModules.length; i++) {
- final ProductModule product = productModules[i];
- final Kind kind = Result.kind(Result.NORMAL, product.assembleAll);
- Result result = product.module.getResult(kind);
- String targPath = Util.path(targDirPath, product.relativePath);
- File jarFile = result.getOutputFile();
- copyFile(jarFile, new File(targPath), FILTER_OFF);
- }
- handler.log("created product in " + targDir);
-
- // ---- create installer
- if (buildSpec.createInstaller) {
- return buildInstaller(buildSpec, targDirPath);
- } else {
- return true;
- }
- }
-
- protected boolean copyBinaries(BuildSpec buildSpec, File distDir,
- File targDir, String excludes) {
- String includes = Builder.BINARY_SOURCE_PATTERN;
- return copyFiles(distDir, targDir, includes, excludes, FILTER_OFF);
- }
-
- /**
- * filter-copy everything but the binaries
- */
- protected boolean copyNonBinaries(BuildSpec buildSpec, File distDir,
- File targDir) {
- String excludes = Builder.BINARY_SOURCE_PATTERN;
- String includes = Builder.ALL_PATTERN;
- return copyFiles(distDir, targDir, includes, excludes, FILTER_ON);
- }
-
- protected final boolean buildProductModule(ProductModule module) {
- ArrayList errors = new ArrayList();
- try {
- Kind productKind = Result.kind(Result.NORMAL, Result.ASSEMBLE);
- Result result = module.module.getResult(productKind);
- return buildAll(result, errors);
- } finally {
- for (Iterator iter = errors.iterator(); iter.hasNext();) {
- handler.error("error building " + module + ": " + iter.next());
- }
- }
- }
-
- /**
- * Discover any modules that might need to be built in order to assemble the
- * product distribution. This interprets empty .jar files as module
- * deliverables.
- */
- protected ProductModule[] discoverModules(File productDir, Modules modules) {
- final ArrayList<File> found = new ArrayList<File>();
- FileFilter filter = new FileFilter() {// empty jar files
- public boolean accept(File file) {
- if ((null != file) && file.canRead()
- && file.getPath().endsWith(".jar") // XXXFileLiteral
- && (0l == file.length())) {
- found.add(file);
- }
- return true;
- }
- };
- Util.visitFiles(productDir, filter);
- ArrayList<ProductModule> results = new ArrayList<ProductModule>();
- for (File file: found) {
- String jarName = moduleAliasFor(file.getName().toLowerCase());
- if (jarName.endsWith(".jar") || jarName.endsWith(".zip")) { // XXXFileLiteral
- jarName = jarName.substring(0, jarName.length() - 4);
- } else {
- handler.log("can only replace .[jar|zip]: " + file);
- // XXX error?
- }
- boolean assembleAll = jarName.endsWith("-all");
- // XXXFileLiteral
- String name = (!assembleAll ? jarName : jarName.substring(0,
- jarName.length() - 4));
- Module module = modules.getModule(name);
- if (null == module) {
- handler.log("unable to find module for " + file);
- } else {
- results.add(new ProductModule(productDir, file, module,
- assembleAll));
- }
- }
- return (ProductModule[]) results.toArray(new ProductModule[0]);
- }
-
- /**
- * Subclasses should query whether to include library files in the assembly.
- *
- * @param module
- * the Module being built
- * @param libraries
- * the List of File path to the jar to consider assembling
- * @return true if the jar should be included, false otherwise.
- */
- protected void removeLibraryFilesToSkip(Module module, List libraries) {
- for (ListIterator liter = libraries.listIterator(); liter.hasNext();) {
- File library = (File) liter.next();
- final String fname = library.getName();
- if (null != fname) {
- for (Iterator iter = SKIP_LIBRARIES.iterator(); iter.hasNext();) {
- String name = (String) iter.next();
- if (fname.equals(name)) {
- liter.remove();
- break;
- }
- }
- }
- }
- }
-
- /**
- * @return String[] names of results to build for this module
- */
- abstract protected Result[] getAntecedantResults(Result toBuild);
-
- /**
- * Compile module classes to classesDir, saving String errors.
- *
- * @param module
- * the Module to compile
- * @param classesDir
- * the File directory to compile to
- * @param useExistingClasses
- * if true, don't recompile and ensure classes are available
- * @param errors
- * the List to add error messages to
- */
- abstract protected boolean compile(Result result, File classesDir,
- boolean useExistingClasses, List<String> errors);
-
- /**
- * Assemble the module distribution from the classesDir, saving String
- * errors.
- *
- * @see #removeLibraryFilesToSkip(Module, File)
- */
- abstract protected boolean assemble(Result result, File classesDir,
- List<String> errors);
-
- /**
- * Assemble the module distribution from the classesDir and all
- * antecendants, saving String errors.
- *
- * @see #removeLibraryFilesToSkip(Module, File)
- */
- abstract protected boolean assembleAll(Result result, Messager handler);
-
- /**
- * Generate the installer for this product to targDirPath
- */
- abstract protected boolean buildInstaller(BuildSpec buildSpec,
- String targDirPath);
-
- /**
- * Copy fromFile to toFile, optionally filtering contents
- */
- abstract protected boolean copyFile(File fromFile, File toFile,
- boolean filter);
-
- /**
- * Copy toDir any fromDir included files without any exluded files,
- * optionally filtering contents.
- *
- * @param fromDir
- * File dir to read from - error if not readable
- * @param toDir
- * File dir to write to - error if not writable
- * @param included
- * String Ant pattern of included files (if null, include all)
- * @param excluded
- * String Ant pattern of excluded files (if null, exclude none)
- * @param filter
- * if FILTER_ON, then filter file contents using global
- * token/value pairs
- */
- abstract protected boolean copyFiles(File fromDir, File toDir,
- String included, String excluded, boolean filter);
- }
|