337 lines
10 KiB
Java
337 lines
10 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.build;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import org.bouncycastle.crypto.digests.MD5Digest;
|
|
|
|
import com.esotericsoftware.wildcard.Paths;
|
|
import com.twmacinta.util.MD5;
|
|
|
|
import dorkbox.Build;
|
|
import dorkbox.BuildOptions;
|
|
import dorkbox.util.Base64Fast;
|
|
import dorkbox.util.FileUtil;
|
|
|
|
public abstract class ProjectBasics {
|
|
public static final String NO_PATH_TOKEN = "NO_PATH_TOKEN";
|
|
|
|
public static final String Java_Pattern = "**" + File.separator + "*.java";
|
|
public static final String Jar_Pattern = "**" + File.separator + "*.jar";
|
|
public static final String STAGING = "staging";
|
|
|
|
public static Map<String, ProjectBasics> deps = new LinkedHashMap<String, ProjectBasics>();
|
|
private static Set<String> buildList = new HashSet<String>();
|
|
|
|
private static boolean forceRebuild = false;
|
|
private static boolean alreadyChecked = false;
|
|
|
|
public static List<File> builderFiles = new ArrayList<File>();
|
|
|
|
{
|
|
// check to see if our deploy code has changed. if yes, then we have to rebuild everything since
|
|
// we don't know what might have changed.
|
|
Paths paths = new Paths();
|
|
File file = new File(ProjectBasics.class.getSimpleName() + ".java").getAbsoluteFile().getParentFile();
|
|
paths.glob(file.getAbsolutePath(), Java_Pattern);
|
|
|
|
for (File f : builderFiles) {
|
|
paths.glob(f.getAbsolutePath(), Java_Pattern);
|
|
}
|
|
|
|
try {
|
|
String oldHash = Build.settings.get("BUILD", String.class);
|
|
String hashedContents = generateChecksums(paths);
|
|
|
|
if (oldHash != null) {
|
|
if (!oldHash.equals(hashedContents)) {
|
|
forceRebuild = true;
|
|
}
|
|
} else {
|
|
forceRebuild = true;
|
|
}
|
|
|
|
if (forceRebuild) {
|
|
if (!alreadyChecked) {
|
|
alreadyChecked = true;
|
|
System.err.println("-= Build system changed. Rebuilding =-");
|
|
}
|
|
Build.settings.save("BUILD", hashedContents);
|
|
}
|
|
} catch (IOException e) {
|
|
}
|
|
}
|
|
|
|
|
|
// removes all saved checksums as well as dependencies. Used to "reset everything", similar to if it was relaunched.
|
|
public static void reset() {
|
|
deps.clear();
|
|
buildList.clear();
|
|
}
|
|
|
|
public String name;
|
|
|
|
protected Paths extraFiles = new Paths();
|
|
|
|
public String outputFile;
|
|
public String outputDir;
|
|
|
|
protected Set<String> dependencies;
|
|
|
|
private transient Paths checksumPaths = new Paths();
|
|
|
|
|
|
public static ProjectBasics get(String projectName) {
|
|
if (deps.containsKey(projectName)) {
|
|
ProjectBasics project = deps.get(projectName);
|
|
// put swt lib into jar!
|
|
return project;
|
|
} else {
|
|
throw new IllegalArgumentException(projectName + " project must exist!");
|
|
}
|
|
}
|
|
|
|
public static void buildAll(BuildOptions properties) throws Exception {
|
|
for (ProjectBasics project : deps.values()) {
|
|
ProjectBasics.build(project, properties);
|
|
}
|
|
}
|
|
|
|
public static void build(String projectName, BuildOptions properties) throws Exception {
|
|
ProjectBasics project = get(projectName);
|
|
|
|
if (project != null) {
|
|
project.build(properties);
|
|
} else {
|
|
System.err.println("Project is NULL. Aborting build.");
|
|
}
|
|
}
|
|
|
|
public static void build(ProjectBasics project, BuildOptions properties) throws Exception {
|
|
project.build(properties);
|
|
}
|
|
|
|
public static void remove(String outputDir) {
|
|
deps.remove(outputDir);
|
|
}
|
|
|
|
protected ProjectBasics(String projectName) {
|
|
this.name = projectName;
|
|
|
|
String lowerCase_outputDir = projectName.toLowerCase();
|
|
this.outputDir = FileUtil.normalizeAsFile(STAGING + File.separator + lowerCase_outputDir);
|
|
|
|
String outputFile = lowerCase_outputDir.substring(lowerCase_outputDir.lastIndexOf("/") + 1, lowerCase_outputDir.length());
|
|
this.outputFile = outputFile + getExtension();
|
|
}
|
|
|
|
public ProjectBasics depends(String dependsProjectName) {
|
|
if (dependsProjectName == null) {
|
|
throw new NullPointerException("Dependencies cannot be null!");
|
|
}
|
|
|
|
if (this.dependencies == null) {
|
|
this.dependencies = new HashSet<String>(2);
|
|
}
|
|
this.dependencies.add(dependsProjectName);
|
|
|
|
return this;
|
|
}
|
|
|
|
public ProjectBasics output() {
|
|
String lowerCase_outputDir = this.outputDir.toLowerCase();
|
|
this.outputDir = STAGING + File.separator + lowerCase_outputDir;
|
|
|
|
String outputFile = lowerCase_outputDir.substring(lowerCase_outputDir.lastIndexOf("/") + 1, lowerCase_outputDir.length());
|
|
this.outputFile = outputFile + getExtension();
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Checks to see if we already built this project. Also, will automatically build this projects
|
|
* dependencies (if they haven't already been built).
|
|
*
|
|
* @return true if we can skip building this project
|
|
*/
|
|
protected boolean checkAndBuildDependencies(BuildOptions properties) throws Exception {
|
|
// exit early if we already built this project
|
|
if (buildList.contains(this.outputDir)) {
|
|
Build.log().message("Skipped (built this run)");
|
|
return true;
|
|
}
|
|
|
|
buildList.add(this.outputDir);
|
|
|
|
// ONLY build the dependencies as well
|
|
if (this.dependencies != null) {
|
|
for (String dep : this.dependencies) {
|
|
ProjectBasics project = deps.get(dep);
|
|
if (!buildList.contains(project.outputDir)) {
|
|
project.build(properties);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** extra files to include when you jar the project */
|
|
public ProjectBasics extraFiles(Paths filePaths) {
|
|
this.extraFiles.add(filePaths);
|
|
|
|
return this;
|
|
}
|
|
|
|
protected abstract ProjectBasics build(BuildOptions properties) throws Exception;
|
|
protected abstract String getExtension();
|
|
|
|
|
|
|
|
/**
|
|
* Add a path to be checksum'd.
|
|
*/
|
|
public final void checksum(Paths path) {
|
|
this.checksumPaths.add(path);
|
|
}
|
|
|
|
/**
|
|
* @return true if the checksums for path match the saved checksums and the jar file exists
|
|
*/
|
|
boolean verifyChecksums(BuildOptions properties) throws IOException {
|
|
if (forceRebuild || properties.compiler.forceRebuild) {
|
|
return false;
|
|
}
|
|
|
|
// check to see if our SOURCES *and check-summed files* have changed.
|
|
String hashedContents = generateChecksums(this.checksumPaths);
|
|
String checkContents = Build.settings.get(this.name, String.class);
|
|
|
|
return hashedContents != null && hashedContents.equals(checkContents);
|
|
}
|
|
|
|
/**
|
|
* Saves the checksums for a given path
|
|
*/
|
|
void saveChecksums() throws IOException {
|
|
// hash/save the sources *and check-summed files* files
|
|
String hashedContents = generateChecksums(this.checksumPaths);
|
|
Build.settings.save(this.name, hashedContents);
|
|
}
|
|
|
|
/**
|
|
* Generates checksums for the given path
|
|
*/
|
|
public static final String generateChecksum(File file) throws IOException {
|
|
synchronized (ProjectBasics.class) {
|
|
// calculate the hash of file
|
|
boolean found = false;
|
|
if (file.isFile() && file.canRead()) {
|
|
found = true;
|
|
}
|
|
|
|
if (!found) {
|
|
return null;
|
|
}
|
|
|
|
byte[] hashBytes = MD5.getHash(file);
|
|
|
|
String fileChecksums = Base64Fast.encodeToString(hashBytes, false);
|
|
return fileChecksums;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates checksums for the given path
|
|
*/
|
|
public static final String generateChecksums(Paths... paths) throws IOException {
|
|
synchronized (ProjectBasics.class) {
|
|
// calculate the hash of all the files in the source path
|
|
Set<String> names = new HashSet<String>(64);
|
|
|
|
for (Paths path : paths) {
|
|
names.addAll(path.getPaths());
|
|
}
|
|
|
|
// hash of hash of files. faster than using java to hash files
|
|
MD5Digest md5_digest = new MD5Digest();
|
|
|
|
boolean found = false;
|
|
for (String name : names) {
|
|
File file = new File(name);
|
|
if (file.isFile() && file.canRead()) {
|
|
found = true;
|
|
|
|
byte[] hashBytes = MD5.getHash(file);
|
|
md5_digest.update(hashBytes, 0, hashBytes.length);
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
return null;
|
|
}
|
|
|
|
byte[] hashBytes = new byte[md5_digest.getDigestSize()];
|
|
md5_digest.doFinal(hashBytes, 0);
|
|
|
|
String fileChecksums = Base64Fast.encodeToString(hashBytes, false);
|
|
return fileChecksums;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return this.name;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
final int prime = 31;
|
|
int result = 1;
|
|
result = prime * result + (this.name == null ? 0 : this.name.hashCode());
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (obj == null) {
|
|
return false;
|
|
}
|
|
if (getClass() != obj.getClass()) {
|
|
return false;
|
|
}
|
|
ProjectBasics other = (ProjectBasics) obj;
|
|
if (this.name == null) {
|
|
if (other.name != null) {
|
|
return false;
|
|
}
|
|
} else if (!this.name.equals(other.name)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
} |