433 lines
16 KiB
Java
433 lines
16 KiB
Java
/*
|
|
* Copyright 2012 dorkbox, llc
|
|
*
|
|
* 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 dorkbox;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.PrintStream;
|
|
import java.lang.annotation.ElementType;
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.lang.annotation.Target;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
|
|
import com.esotericsoftware.wildcard.Paths;
|
|
|
|
import dorkbox.build.ProjectBasics;
|
|
import dorkbox.build.ProjectJava;
|
|
import dorkbox.build.SimpleArgs;
|
|
import dorkbox.build.util.BuildLog;
|
|
import dorkbox.build.util.BuildParser;
|
|
import dorkbox.build.util.ByteClassloader;
|
|
import dorkbox.build.util.ClassByteIterator;
|
|
import dorkbox.build.util.jar.Pack200Util;
|
|
import dorkbox.util.FileUtil;
|
|
import dorkbox.util.LZMA;
|
|
import dorkbox.util.LocationResolver;
|
|
import dorkbox.util.OS;
|
|
import dorkbox.util.Sys;
|
|
import dorkbox.util.annotation.AnnotationDefaults;
|
|
import dorkbox.util.annotation.AnnotationDetector;
|
|
import dorkbox.util.properties.PropertiesProvider;
|
|
|
|
public class Build {
|
|
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
@Target({ElementType.TYPE})
|
|
public @interface Builder {
|
|
}
|
|
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
@Target({ElementType.TYPE})
|
|
public @interface Configure {
|
|
}
|
|
|
|
public static final String BUILD_MODE = "build";
|
|
|
|
/** Location where settings are stored */
|
|
public static PropertiesProvider settings = new PropertiesProvider(new File("settings.ini"));
|
|
|
|
private ByteClassloader classloader;
|
|
|
|
static {
|
|
Paths.setDefaultGlobExcludes("**/.svn/**, **/.git/**");
|
|
}
|
|
|
|
public static void main(String[] _args) {
|
|
for (int i=0;i<_args.length;i++) {
|
|
_args[i] = _args[i].toLowerCase();
|
|
}
|
|
|
|
if (_args.length < 2) {
|
|
System.err.println("You must specify an action, followed by what you want done.");
|
|
System.err.println("For example: build myProject , which will then find and build your project");
|
|
return;
|
|
}
|
|
|
|
SimpleArgs args = new SimpleArgs(_args);
|
|
|
|
System.err.println("Dorkbox OAK: starting " + args);
|
|
|
|
Build build = new Build();
|
|
try {
|
|
build.prepareXcompile();
|
|
build.loadBuildInfo(args);
|
|
build.start(args);
|
|
} catch (Throwable e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
// loads the build.oak file information
|
|
private void loadBuildInfo(SimpleArgs args) throws Exception {
|
|
HashMap<String, Object> data = BuildParser.parse(args);
|
|
|
|
|
|
Paths classPaths = BuildParser.getPathsFromMap(data, "classpath");
|
|
Paths sourcePaths = BuildParser.getPathsFromMap(data, "source");
|
|
|
|
// always use these as the default. don't want the runtimes on our path
|
|
classPaths.glob("libs", "**/*.jar", "!jdkRuntimes");
|
|
|
|
|
|
String projectName = "project";
|
|
|
|
if (data.containsKey("name")) {
|
|
Object object = data.get("name");
|
|
projectName = (String) object;
|
|
}
|
|
|
|
ByteClassloader bytesClassloader = new ByteClassloader(Thread.currentThread().getContextClassLoader());
|
|
|
|
ProjectJava project = ProjectJava.create(projectName)
|
|
.classPath(classPaths)
|
|
.compilerClassloader(bytesClassloader)
|
|
.sourcePath(sourcePaths);
|
|
|
|
|
|
try {
|
|
log().println("-= Compiling build instructions =-" + OS.LINE_SEPARATOR);
|
|
BuildLog.stop();
|
|
project.forceBuild(new BuildOptions(), false, false);
|
|
ProjectBasics.reset();
|
|
BuildLog.start();
|
|
} catch (Exception e) {
|
|
BuildLog.start();
|
|
throw e;
|
|
}
|
|
|
|
this.classloader = bytesClassloader;
|
|
}
|
|
|
|
private Build() {
|
|
}
|
|
|
|
public static BuildLog log() {
|
|
return new BuildLog();
|
|
}
|
|
|
|
public static BuildLog log(PrintStream printer) {
|
|
return new BuildLog(printer);
|
|
}
|
|
|
|
private void start(SimpleArgs args) throws IOException, IllegalAccessException, IllegalArgumentException,
|
|
InvocationTargetException, InstantiationException {
|
|
|
|
BuildOptions buildOptions = new BuildOptions();
|
|
List<Class<?>> controllers = AnnotationDetector.scan(this.classloader, new ClassByteIterator(this.classloader, null))
|
|
.forAnnotations(Build.Configure.class)
|
|
.collect(AnnotationDefaults.getType);
|
|
|
|
if (controllers != null) {
|
|
// do we have something to control the build process??
|
|
// now we want to update/search for all project builders if we didn't already run our specific builder
|
|
for (Class<?> c : controllers) {
|
|
Class<?>[] params = new Class<?>[] {BuildOptions.class, SimpleArgs.class};
|
|
Method buildTargeted = null;
|
|
|
|
// setup(BuildOptions, Args)
|
|
try {
|
|
buildTargeted = c.getMethod("setup", params);
|
|
} catch (Exception e) {}
|
|
|
|
if (buildTargeted != null) {
|
|
Object newInstance = c.newInstance();
|
|
// see if we can build a targeted build
|
|
buildTargeted.invoke(newInstance, buildOptions, args);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// now we want to update/search for all project builders.
|
|
if (args.getMode().equals(Build.BUILD_MODE)) {
|
|
boolean found = false;
|
|
List<Class<?>> builders = AnnotationDetector.scan(this.classloader, new ClassByteIterator(this.classloader, null))
|
|
.forAnnotations(Build.Builder.class)
|
|
.collect(AnnotationDefaults.getType);
|
|
|
|
if (builders != null) {
|
|
String projectToBuild = args.get(1);
|
|
for (Class<?> c : builders) {
|
|
String simpleName = c.getSimpleName().toLowerCase();
|
|
|
|
if (projectToBuild.equals(simpleName)) {
|
|
Method build = null;
|
|
|
|
// 4 different build methods supported.
|
|
// build()
|
|
try {
|
|
build = c.getMethod(Build.BUILD_MODE, new Class<?>[] {});
|
|
} catch (Exception e) {}
|
|
|
|
if (build != null) {
|
|
build .invoke(c);
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
// build(Args)
|
|
Class<?>[] params = new Class<?>[] {SimpleArgs.class};
|
|
try {
|
|
build = c.getMethod(Build.BUILD_MODE, params);
|
|
} catch (Exception e) {}
|
|
|
|
if (build != null) {
|
|
build .invoke(c, args);
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
|
|
// build(BuildOptions)
|
|
params = new Class<?>[] {BuildOptions.class};
|
|
try {
|
|
build = c.getMethod(Build.BUILD_MODE, params);
|
|
} catch (Exception e) {}
|
|
|
|
if (build != null) {
|
|
build.invoke(c, buildOptions);
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
|
|
// build(BuildOptions, Args)
|
|
params = new Class<?>[] {BuildOptions.class, SimpleArgs.class};
|
|
try {
|
|
build = c.getMethod(Build.BUILD_MODE, params);
|
|
} catch (Exception e) {}
|
|
|
|
if (build != null) {
|
|
build .invoke(c, buildOptions, args);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
System.err.println("Unable to find a build for the target: " + args);
|
|
}
|
|
}
|
|
|
|
if (controllers != null) {
|
|
// do we have something to control the build process??
|
|
// now we want to update/search for all project builders if we didn't already run our specific builder
|
|
for (Class<?> c : controllers) {
|
|
Class<?>[] params = new Class<?>[] {BuildOptions.class, SimpleArgs.class};
|
|
Method buildTargeted = null;
|
|
|
|
// finish(BuildOptions, Args)
|
|
try {
|
|
buildTargeted = c.getMethod("takedown", params);
|
|
} catch (Exception e) {}
|
|
|
|
if (buildTargeted != null) {
|
|
Object newInstance = c.newInstance();
|
|
// see if we can build a targeted build
|
|
buildTargeted.invoke(newInstance, buildOptions, args);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* check to see if our jdk files have been decompressed (necessary for cross target builds)
|
|
*/
|
|
private void prepareXcompile() throws IOException {
|
|
String jdkDist = FileUtil.normalizeAsFile(Build.path("libs", "jdkRuntimes"));
|
|
List<File> jdkFiles = FileUtil.parseDir(jdkDist);
|
|
boolean first = true;
|
|
for (File f : jdkFiles) {
|
|
// unLZMA + unpack200
|
|
String name = f.getName();
|
|
String suffix = ".pack.lzma";
|
|
|
|
if (name.endsWith(suffix)) {
|
|
int nameLength = f.getAbsolutePath().length();
|
|
String fixedName = f.getAbsolutePath().substring(0, nameLength - suffix.length());
|
|
File file = new File(fixedName);
|
|
|
|
if (!file.canRead() || file.length() == 0) {
|
|
if (first) {
|
|
first = false;
|
|
System.err.println("******************************************");
|
|
System.err.println("* Dorkbox OAK -- Preparing environment *");
|
|
System.err.println("******************************************");
|
|
}
|
|
|
|
System.err.println("* Decompressing: " + f.getAbsolutePath());
|
|
InputStream inputStream = new FileInputStream(f);
|
|
// now uncompress
|
|
ByteArrayOutputStream outputStream = LZMA.decode(inputStream);
|
|
|
|
// now unpack
|
|
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
|
outputStream = Pack200Util.Java.unpack200((ByteArrayInputStream) inputStream);
|
|
|
|
// now write to disk
|
|
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
|
|
|
FileOutputStream fileOutputStream = new FileOutputStream(new File(fixedName));
|
|
Sys.copyStream(inputStream, fileOutputStream);
|
|
Sys.close(fileOutputStream);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!first) {
|
|
System.err.println("* Finished preparing environment");
|
|
System.err.println("******************************************\n\n");
|
|
}
|
|
}
|
|
|
|
public static String path(String... paths) {
|
|
StringBuilder buffer = new StringBuilder(128);
|
|
|
|
for (String p : paths) {
|
|
buffer.append(p).append(File.separator);
|
|
}
|
|
|
|
int length = buffer.length();
|
|
buffer.delete(length-1, length);
|
|
|
|
return buffer.toString();
|
|
}
|
|
|
|
/**
|
|
* Converts a class to it's .java file.
|
|
*/
|
|
public static Paths getClassPath(Class<?> clazz) throws IOException {
|
|
String rootPath = LocationResolver.get(clazz).getAbsolutePath();
|
|
|
|
String fileName = clazz.getCanonicalName();
|
|
String convert = fileName.replace('.', File.separatorChar) + ".java";
|
|
File rootFile = new File(rootPath);
|
|
|
|
if (rootFile.isDirectory()) {
|
|
File location = rootFile.getParentFile();
|
|
location = new File(location, "src");
|
|
|
|
Paths path = new Paths(location.getAbsolutePath(), convert);
|
|
return path;
|
|
} else {
|
|
throw new IOException("we can only support listing files that are not in a container!");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets all of the .java files accessible which belong to the
|
|
* package and subpackages of the given class
|
|
*/
|
|
public static Paths getClassPathPackage(Class<?> clazz) throws IOException {
|
|
String rootPath = LocationResolver.get(clazz).getAbsolutePath();
|
|
|
|
String dirName = clazz.getPackage().getName();
|
|
String convert = dirName.replace('.', File.separatorChar);
|
|
File rootFile = new File(rootPath);
|
|
|
|
if (rootFile.isDirectory()) {
|
|
File location = rootFile.getParentFile();
|
|
location = new File(location, "src");
|
|
location = new File(location, convert);
|
|
|
|
Paths paths = new Paths(location.getAbsolutePath(), "**.java");
|
|
return paths;
|
|
} else {
|
|
throw new IOException("we can only support listing class path packages that are not in a container!");
|
|
}
|
|
}
|
|
|
|
public static final void finish(String text) {
|
|
System.err.println("\n\n");
|
|
System.err.println("TIME: " + new Date());
|
|
System.err.println("FINISHED: " + text);
|
|
}
|
|
|
|
public static File moveFile(String source, String target) throws IOException {
|
|
source = FileUtil.normalizeAsFile(source);
|
|
target = FileUtil.normalizeAsFile(target);
|
|
|
|
|
|
log().title(" Moving file").message(" ╭─ " + source,
|
|
"╰─> " + target);
|
|
|
|
return FileUtil.moveFile(source, target);
|
|
}
|
|
|
|
public static File copyFile(File source, File target) throws IOException {
|
|
source = FileUtil.normalize(source);
|
|
target = FileUtil.normalize(target);
|
|
|
|
|
|
log().title(" Copying file").message(" ╭─ " + source.getAbsolutePath(),
|
|
"╰─> " + target.getAbsolutePath());
|
|
|
|
return FileUtil.copyFile(source, target);
|
|
}
|
|
|
|
public static void copyFile(String source, String target) throws IOException {
|
|
source = FileUtil.normalizeAsFile(source);
|
|
target = FileUtil.normalizeAsFile(target);
|
|
|
|
log().title(" Copying file").message(" ╭─ " + source,
|
|
"╰─> " + target);
|
|
|
|
FileUtil.copyFile(source, target);
|
|
}
|
|
|
|
public static void copyDirectory(String source, String target, String... dirNamesToIgnore) throws IOException {
|
|
source = FileUtil.normalizeAsFile(source);
|
|
target = FileUtil.normalizeAsFile(target);
|
|
|
|
log().title(" Copying dir").message(" ╭─ " + source,
|
|
"╰─> " + target);
|
|
FileUtil.copyDirectory(source, target, dirNamesToIgnore);
|
|
}
|
|
}
|