8004658: Add internal smart javac wrapper to solve JEP 139

Reviewed-by: jjg
This commit is contained in:
Fredrik Öhrström 2013-01-18 00:16:21 +01:00
parent 0a95b1d28c
commit 3d5f55b851
30 changed files with 7449 additions and 7 deletions

View File

@ -29,18 +29,18 @@
# Override this path as needed, either on the command line or in
# one of the standard user build.properties files (see build.xml)
# boot.java.home = /opt/jdk/1.6.0
# boot.java.home = /opt/jdk/1.7.0
boot.java = ${boot.java.home}/bin/java
boot.javac = ${boot.java.home}/bin/javac
boot.javac.source = 6
boot.javac.target = 6
boot.javac.source = 7
boot.javac.target = 7
# This is the JDK used to run the product version of the tools,
# for example, for testing. If you're building a complete JDK, specify that.
# Override this path as needed, either on the command line or in
# one of the standard user build.properties files (see build.xml)
# target.java.home = /opt/jdk/1.7.0
# target.java.home = /opt/jdk/1.8.0
target.java = ${target.java.home}/bin/java
# Version info -- override as needed
@ -161,6 +161,14 @@ javap.tests = \
#
sjavac.includes = \
com/sun/tools/sjavac/
sjavac.tests = \
tools/sjavac
#
# The following files require the latest JDK to be available.
# The API can be provided by using a suitable boot.java.home
# or by setting import.jdk

View File

@ -241,15 +241,15 @@
</target>
<target name="build-bootstrap-tools"
depends="build-bootstrap-javac,build-bootstrap-javadoc,build-bootstrap-doclets,build-bootstrap-javah"
depends="build-bootstrap-javac,build-bootstrap-javadoc,build-bootstrap-doclets,build-bootstrap-javah,build-bootstrap-sjavac"
/>
<target name="build-all-tools"
depends="build-javac,build-javadoc,build-doclets,build-javah,build-javap"
depends="build-javac,build-javadoc,build-doclets,build-javah,build-javap,build-sjavac"
/>
<target name="build-all-classes" depends="build-bootstrap-javac,-create-import-jdk-stubs">
<build-classes includes="${javac.includes} ${javadoc.includes} ${doclets.includes} ${javah.includes} ${javap.includes}"/>
<build-classes includes="${javac.includes} ${javadoc.includes} ${doclets.includes} ${javah.includes} ${javap.includes} ${sjavac.includes}"/>
</target>
<!-- clean -->
@ -656,6 +656,40 @@
<target name="javap" depends="build-javap,jtreg-javap,findbugs-javap"/>
<!--
**** sjavac targets.
-->
<target name="build-bootstrap-sjavac"
depends="-def-build-bootstrap-classes,-def-build-bootstrap-jar,-def-build-bootstrap-tool">
<build-bootstrap-classes includes="${sjavac.includes}"/>
<build-bootstrap-jar name="sjavac" includes="${sjavac.includes}"
jarmainclass="com.sun.tools.sjavac.Main"/>
<build-bootstrap-tool name="sjavac"/>
</target>
<target name="build-classes-sjavac" depends="build-classes-javac">
<build-classes includes="${sjavac.includes}"/>
</target>
<target name="build-sjavac" depends="build-classes-sjavac">
<build-jar name="sjavac" includes="${sjavac.includes}"
jarmainclass="com.sun.tools.sjavac.Main"
jarclasspath="sjavac.jar"/>
<build-tool name="sjavac"/>
</target>
<!-- (no javadoc for javap) -->
<target name="jtreg-sjavac" depends="build-sjavac,-def-jtreg">
<jtreg-tool name="sjavac" tests="${sjavac.tests}"/>
</target>
<target name="findbugs-sjavac" depends="build-sjavac,-def-findbugs">
<findbugs-tool name="sjavac"/>
</target>
<target name="sjavac" depends="build-sjavac,jtreg-sjavac,findbugs-sjavac"/>
<!--
**** Create import JDK stubs.

View File

@ -0,0 +1,275 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* The build state class captures the source code and generated artifacts
* from a build. There are usually two build states, the previous one (prev),
* loaded from the javac_state file, and the current one (now).
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class BuildState {
private Map<String,Module> modules = new HashMap<String,Module>();
private Map<String,Package> packages = new HashMap<String,Package>();
private Map<String,Source> sources = new HashMap<String,Source>();
private Map<String,File> artifacts = new HashMap<String,File>();
// Map from package to a set of packages that depend on said package.
private Map<String,Set<String>> dependents = new HashMap<String,Set<String>>();
public Map<String,Module> modules() { return modules; }
public Map<String,Package> packages() { return packages; }
public Map<String,Source> sources() { return sources; }
public Map<String,File> artifacts() { return artifacts; }
public Map<String,Set<String>> dependents() { return dependents; }
/**
* Lookup a module from a name. Create the module if it does
* not exist yet.
*/
public Module lookupModule(String mod) {
Module m = modules.get(mod);
if (m == null) {
m = new Module(mod, "???");
modules.put(mod, m);
}
return m;
}
/**
* Find a module from a given package name. For example:
* The package name "base:java.lang" will fetch the module named "base".
* The package name ":java.net" will fetch the default module.
*/
Module findModuleFromPackageName(String pkg) {
int cp = pkg.indexOf(':');
assert(cp != -1);
String mod = pkg.substring(0, cp);
return lookupModule(mod);
}
/**
* Collect all packages, sources and artifacts for all modules
* into the build state.
*
* @param m The set of modules.
*/
public void collectPackagesSourcesAndArtifacts(Map<String,Module> m) {
modules = m;
// Extract all the found packages.
for (Module i : modules.values()) {
for (Map.Entry<String,Package> j : i.packages().entrySet()) {
Package p = packages.get(j.getKey());
// Check that no two different packages are stored under same name.
assert(p == null || p == j.getValue());
if (p == null) {
p = j.getValue();
packages.put(j.getKey(),j.getValue());
}
for (Map.Entry<String,Source> k : p.sources().entrySet()) {
Source s = sources.get(k.getKey());
// Check that no two different sources are stored under same name.
assert(s == null || s == k.getValue());
if (s == null) {
s = k.getValue();
sources.put(k.getKey(), k.getValue());
}
}
for (Map.Entry<String,File> g : p.artifacts().entrySet()) {
File f = artifacts.get(g.getKey());
// Check that no two artifacts are stored under the same file.
assert(f == null || f == g.getValue());
if (f == null) {
f = g.getValue();
artifacts.put(g.getKey(), g.getValue());
}
}
}
}
}
/**
* Collect all the artifacts of all modules and packages.
*
* @param m The set of modules.
*/
public void collectArtifacts(Map<String,Module> m) {
modules = m;
// Extract all the found packages.
for (Module i : modules.values()) {
for (Map.Entry<String,Package> j : i.packages().entrySet()) {
Package p = packages.get(j.getKey());
// Check that no two different packages are stored under same name.
assert(p == null || p == j.getValue());
p = j.getValue();
packages.put(j.getKey(),j.getValue());
for (Map.Entry<String,File> g : p.artifacts().entrySet()) {
File f = artifacts.get(g.getKey());
// Check that no two artifacts are stored under the same file.
assert(f == null || f == g.getValue());
artifacts.put(g.getKey(), g.getValue());
}
}
}
}
/**
* Calculate the package dependents (ie the reverse of the dependencies).
*/
public void calculateDependents() {
dependents = new HashMap<String,Set<String>>();
for (String s : packages.keySet()) {
Package p = packages.get(s);
for (String d : p.dependencies()) {
Set<String> ss = dependents.get(d);
if (ss == null) {
ss = new HashSet<String>();
dependents.put(d, ss);
}
// Add the dependent information to the global dependent map.
ss.add(s);
Package dp = packages.get(d);
// Also add the dependent information to the package specific map.
// Normally, you do not compile java.lang et al. Therefore
// there are several packages that p depends upon that you
// do not have in your state database. This is perfectly fine.
if (dp != null) {
// But this package did exist in the state database.
dp.addDependent(p.name());
}
}
}
}
/**
* Verify that the setModules method above did the right thing when
* running through the module->package->source structure.
*/
public void checkInternalState(String msg, boolean linkedOnly, Map<String,Source> srcs) {
boolean baad = false;
Map<String,Source> original = new HashMap<String,Source>();
Map<String,Source> calculated = new HashMap<String,Source>();
for (String s : sources.keySet()) {
Source ss = sources.get(s);
if (ss.isLinkedOnly() == linkedOnly) {
calculated.put(s,ss);
}
}
for (String s : srcs.keySet()) {
Source ss = srcs.get(s);
if (ss.isLinkedOnly() == linkedOnly) {
original.put(s,ss);
}
}
if (original.size() != calculated.size()) {
Log.error("INTERNAL ERROR "+msg+" original and calculated are not the same size!");
baad = true;
}
if (!original.keySet().equals(calculated.keySet())) {
Log.error("INTERNAL ERROR "+msg+" original and calculated do not have the same domain!");
baad = true;
}
if (!baad) {
for (String s : original.keySet()) {
Source s1 = original.get(s);
Source s2 = calculated.get(s);
if (s1 == null || s2 == null || !s1.equals(s2)) {
Log.error("INTERNAL ERROR "+msg+" original and calculated have differing elements for "+s);
}
baad = true;
}
}
if (baad) {
for (String s : original.keySet()) {
Source ss = original.get(s);
Source sss = calculated.get(s);
if (sss == null) {
Log.error("The file "+s+" does not exist in calculated tree of sources.");
}
}
for (String s : calculated.keySet()) {
Source ss = calculated.get(s);
Source sss = original.get(s);
if (sss == null) {
Log.error("The file "+s+" does not exist in original set of found sources.");
}
}
}
}
/**
* Load a module from the javac state file.
*/
public Module loadModule(String l) {
Module m = Module.load(l);
modules.put(m.name(), m);
return m;
}
/**
* Load a package from the javac state file.
*/
public Package loadPackage(Module lastModule, String l) {
Package p = Package.load(lastModule, l);
lastModule.addPackage(p);
packages.put(p.name(), p);
return p;
}
/**
* Load a source from the javac state file.
*/
public Source loadSource(Package lastPackage, String l, boolean is_generated) {
Source s = Source.load(lastPackage, l, is_generated);
lastPackage.addSource(s);
sources.put(s.name(), s);
return s;
}
/**
* During an incremental compile we need to copy the old javac state
* information about packages that were not recompiled.
*/
public void copyPackagesExcept(BuildState prev, Set<String> recompiled, Set<String> removed) {
for (String pkg : prev.packages().keySet()) {
// Do not copy recompiled or removed packages.
if (recompiled.contains(pkg) || removed.contains(pkg)) continue;
Module mnew = findModuleFromPackageName(pkg);
Package pprev = prev.packages().get(pkg);
mnew.addPackage(pprev);
}
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright (c) 2001, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.io.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
/**
* The clean properties transform should not be necessary.
* Eventually we will cleanup the property file sources in the OpenJDK instead.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class CleanProperties implements Transformer
{
public void setExtra(String e) {
// Any extra information is ignored for clean properties.
}
public void setExtra(String[] a) {
// Any extra information is ignored for clean properties.
}
public boolean transform(Map<String,Set<URI>> pkgSrcs,
Set<URI> visibleSrcs,
Map<URI,Set<String>> visibleClasses,
Map<String,Set<String>> oldPackageDependencies,
URI destRoot,
Map<String,Set<URI>> packageArtifacts,
Map<String,Set<String>> packageDependencies,
Map<String,String> packagePublicApis,
int debugLevel,
boolean incremental,
int numCores,
PrintStream out,
PrintStream err)
{
boolean rc = true;
for (String pkgName : pkgSrcs.keySet()) {
String pkgNameF = pkgName.replace('.',File.separatorChar);
for (URI u : pkgSrcs.get(pkgName)) {
File src = new File(u);
boolean r = clean(pkgName, pkgNameF, src, new File(destRoot), debugLevel,
packageArtifacts);
if (r == false) {
rc = false;
}
}
}
return rc;
}
boolean clean(String pkgName, String pkgNameF, File src, File destRoot, int debugLevel,
Map<String,Set<URI>> packageArtifacts)
{
// Load the properties file.
Properties p = new Properties();
try {
p.load(new FileInputStream(src));
} catch (IOException e) {
Log.error("Error reading file "+src.getPath());
return false;
}
// Sort the properties in increasing key order.
List<String> sortedKeys = new ArrayList<String>();
for (Object key : p.keySet()) {
sortedKeys.add((String)key);
}
Collections.sort(sortedKeys);
Iterator<String> keys = sortedKeys.iterator();
// Collect the properties into a string buffer.
StringBuilder data = new StringBuilder();
while (keys.hasNext()) {
String key = keys.next();
data.append(CompileProperties.escape(key)+":"+CompileProperties.escape((String)p.get(key))+"\n");
}
String destFilename = destRoot.getPath()+File.separator+pkgNameF+File.separator+src.getName();
File dest = new File(destFilename);
// Make sure the dest directories exist.
if (!dest.getParentFile().isDirectory()) {
if (!dest.getParentFile().mkdirs()) {
Log.error("Could not create the directory "+dest.getParentFile().getPath());
return false;
}
}
Set<URI> as = packageArtifacts.get(pkgName);
if (as == null) {
as = new HashSet<URI>();
packageArtifacts.put(pkgName, as);
}
as.add(dest.toURI());
if (dest.exists() && dest.lastModified() > src.lastModified()) {
// A cleaned property file exists, and its timestamp is newer than the source.
// Assume that we do not need to clean!
// Thus we are done.
return true;
}
Log.info("Cleaning property file "+pkgNameF+File.separator+src.getName());
try (Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(dest)))) {
writer.write(data.toString());
} catch ( IOException e ) {
Log.error("Could not write file "+dest.getPath());
return false;
}
return true;
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.net.URI;
import java.util.HashSet;
import java.util.Set;
/**
* A compile chunk is a list of sources/packages to be compiled. Possibly a subset of
* the total number of sources/packages to be compiled for this sjavac invocation.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class CompileChunk implements Comparable<CompileChunk> {
public int numPackages;
public int numDependents;
public Set<URI> srcs = new HashSet<URI>();
public StringBuilder pkgNames = new StringBuilder();
public String pkgFromTos = "";
public int compareTo(CompileChunk c) {
if (numDependents == c.numDependents) return 0;
if (numDependents > c.numDependents) return -1;
return -1;
}
boolean equal(CompileChunk c) {
return numDependents == c.numDependents;
}
}

View File

@ -0,0 +1,344 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.net.URI;
import java.util.Arrays;
import java.util.Random;
import java.util.Set;
import java.util.Map;
import com.sun.tools.sjavac.server.JavacServer;
import com.sun.tools.sjavac.server.SysInfo;
import java.io.PrintStream;
/**
* This transform compiles a set of packages containing Java sources.
* The compile request is divided into separate sets of source files.
* For each set a separate request thread is dispatched to a javac server
* and the meta data is accumulated. The number of sets correspond more or
* less to the number of cores. Less so now, than it will in the future.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class CompileJavaPackages implements Transformer {
// The current limited sharing of data between concurrent JavaCompilers
// in the server will not give speedups above 3 cores. Thus this limit.
// We hope to improve this in the future.
final static int limitOnConcurrency = 3;
String serverSettings;
public void setExtra(String e) {
serverSettings = e;
}
String[] args;
public void setExtra(String[] a) {
args = a;
}
public boolean transform(Map<String,Set<URI>> pkgSrcs,
Set<URI> visibleSources,
Map<URI,Set<String>> visibleClasses,
Map<String,Set<String>> oldPackageDependents,
URI destRoot,
final Map<String,Set<URI>> packageArtifacts,
final Map<String,Set<String>> packageDependencies,
final Map<String,String> packagePubapis,
int debugLevel,
boolean incremental,
int numCores,
PrintStream out,
PrintStream err)
{
boolean rc = true;
boolean concurrentCompiles = true;
// Fetch the id.
String id = Util.extractStringOption("id", serverSettings);
if (id == null || id.equals("")) {
// No explicit id set. Create a random id so that the requests can be
// grouped properly in the server.
id = "id"+(((new Random()).nextLong())&Long.MAX_VALUE);
}
// Only keep portfile and sjavac settings..
String psServerSettings = Util.cleanSubOptions("--server:", Util.set("portfile","sjavac","background","keepalive"), serverSettings);
// Get maximum heap size from the server!
SysInfo sysinfo = JavacServer.connectGetSysInfo(psServerSettings, out, err);
if (sysinfo.numCores == -1) {
Log.error("Could not query server for sysinfo!");
return false;
}
int numMBytes = (int)(sysinfo.maxMemory / ((long)(1024*1024)));
Log.debug("Server reports "+numMBytes+"MiB of memory and "+sysinfo.numCores+" cores");
if (numCores <= 0) {
// Set the requested number of cores to the number of cores on the server.
numCores = sysinfo.numCores;
Log.debug("Number of jobs not explicitly set, defaulting to "+sysinfo.numCores);
} else if (sysinfo.numCores < numCores) {
// Set the requested number of cores to the number of cores on the server.
Log.debug("Limiting jobs from explicitly set "+numCores+" to cores available on server: "+sysinfo.numCores);
numCores = sysinfo.numCores;
} else {
Log.debug("Number of jobs explicitly set to "+numCores);
}
// More than three concurrent cores does not currently give a speedup, at least for compiling the jdk
// in the OpenJDK. This will change in the future.
int numCompiles = numCores;
if (numCores > limitOnConcurrency) numCompiles = limitOnConcurrency;
// Split the work up in chunks to compiled.
int numSources = 0;
for (String s : pkgSrcs.keySet()) {
Set<URI> ss = pkgSrcs.get(s);
numSources += ss.size();
}
int sourcesPerCompile = numSources / numCompiles;
// For 64 bit Java, it seems we can compile the OpenJDK 8800 files with a 1500M of heap
// in a single chunk, with reasonable performance.
// For 32 bit java, it seems we need 1G of heap.
// Number experimentally determined when compiling the OpenJDK.
// Includes space for reasonably efficient garbage collection etc,
// Calculating backwards gives us a requirement of
// 1500M/8800 = 175 KiB for 64 bit platforms
// and 1G/8800 = 119 KiB for 32 bit platform
// for each compile.....
int kbPerFile = 175;
String osarch = System.getProperty("os.arch");
if (osarch.equals("i386")) {
// For 32 bit platforms, assume it is slightly smaller
// because of smaller object headers and pointers.
kbPerFile = 119;
}
int numRequiredMBytes = (kbPerFile*numSources)/1024;
Log.debug("For os.arch "+osarch+" the empirically determined heap required per file is "+kbPerFile+"KiB");
Log.debug("Server has "+numMBytes+"MiB of heap.");
Log.debug("Heuristics say that we need "+numRequiredMBytes+"MiB of heap for all source files.");
// Perform heuristics to see how many cores we can use,
// or if we have to the work serially in smaller chunks.
if (numMBytes < numRequiredMBytes) {
// Ouch, cannot fit even a single compile into the heap.
// Split it up into several serial chunks.
concurrentCompiles = false;
// Limit the number of sources for each compile to 500.
if (numSources < 500) {
numCompiles = 1;
sourcesPerCompile = numSources;
Log.debug("Compiling as a single source code chunk to stay within heap size limitations!");
} else if (sourcesPerCompile > 500) {
// This number is very low, and tuned to dealing with the OpenJDK
// where the source is >very< circular! In normal application,
// with less circularity the number could perhaps be increased.
numCompiles = numSources / 500;
sourcesPerCompile = numSources/numCompiles;
Log.debug("Compiling source as "+numCompiles+" code chunks serially to stay within heap size limitations!");
}
} else {
if (numCompiles > 1) {
// Ok, we can fit at least one full compilation on the heap.
float usagePerCompile = (float)numRequiredMBytes / ((float)numCompiles * (float)0.7);
int usage = (int)(usagePerCompile * (float)numCompiles);
Log.debug("Heuristics say that for "+numCompiles+" concurrent compiles we need "+usage+"MiB");
if (usage > numMBytes) {
// Ouch it does not fit. Reduce to a single chunk.
numCompiles = 1;
sourcesPerCompile = numSources;
// What if the relationship betweem number of compile_chunks and num_required_mbytes
// is not linear? Then perhaps 2 chunks would fit where 3 does not. Well, this is
// something to experiment upon in the future.
Log.debug("Limiting compile to a single thread to stay within heap size limitations!");
}
}
}
Log.debug("Compiling sources in "+numCompiles+" chunk(s)");
// Create the chunks to be compiled.
final CompileChunk[] compileChunks = createCompileChunks(pkgSrcs, oldPackageDependents,
numCompiles, sourcesPerCompile);
if (Log.isDebugging()) {
int cn = 1;
for (CompileChunk cc : compileChunks) {
Log.debug("Chunk "+cn+" for "+id+" ---------------");
cn++;
for (URI u : cc.srcs) {
Log.debug(""+u);
}
}
}
// The return values for each chunked compile.
final int[] rn = new int[numCompiles];
// The requets, might or might not run as a background thread.
final Thread[] requests = new Thread[numCompiles];
final Set<URI> fvisible_sources = visibleSources;
final Map<URI,Set<String>> fvisible_classes = visibleClasses;
long start = System.currentTimeMillis();
for (int i=0; i<numCompiles; ++i) {
final int ii = i;
final CompileChunk cc = compileChunks[i];
// Pass the num_cores and the id (appended with the chunk number) to the server.
final String cleanedServerSettings = psServerSettings+",poolsize="+numCores+",id="+id+"-"+ii;
final PrintStream fout = out;
final PrintStream ferr = err;
requests[ii] = new Thread() {
@Override
public void run() {
rn[ii] = JavacServer.useServer(cleanedServerSettings,
Main.removeWrapperArgs(args),
cc.srcs,
fvisible_sources,
fvisible_classes,
packageArtifacts,
packageDependencies,
packagePubapis,
null,
fout, ferr);
}
};
if (cc.srcs.size() > 0) {
String numdeps = "";
if (cc.numDependents > 0) numdeps = "(with "+cc.numDependents+" dependents) ";
if (!incremental || cc.numPackages > 16) {
String info = "("+cc.pkgFromTos+")";
if (info.equals("( to )")) {
info = "";
}
Log.info("Compiling "+cc.srcs.size()+" files "+numdeps+"in "+cc.numPackages+" packages "+info);
} else {
Log.info("Compiling "+cc.pkgNames+numdeps);
}
if (concurrentCompiles) {
requests[ii].start();
}
else {
requests[ii].run();
// If there was an error, then stop early when running single threaded.
if (rn[i] != 0) {
return false;
}
}
}
}
if (concurrentCompiles) {
// If there are background threads for the concurrent compiles, then join them.
for (int i=0; i<numCompiles; ++i) {
try { requests[i].join(); } catch (InterruptedException e) { }
}
}
// Check the return values.
for (int i=0; i<numCompiles; ++i) {
if (compileChunks[i].srcs.size() > 0) {
if (rn[i] != 0) {
rc = false;
}
}
}
long duration = System.currentTimeMillis() - start;
long minutes = duration/60000;
long seconds = (duration-minutes*60000)/1000;
Log.debug("Compilation of "+numSources+" source files took "+minutes+"m "+seconds+"s");
return rc;
}
/**
* Split up the sources into compile chunks. If old package dependents information
* is available, sort the order of the chunks into the most dependent first!
* (Typically that chunk contains the java.lang package.) In the future
* we could perhaps improve the heuristics to put the sources into even more sensible chunks.
* Now the package are simple sorted in alphabetical order and chunked, then the chunks
* are sorted on how dependent they are.
*
* @param pkgSrcs The sources to compile.
* @param oldPackageDependents Old package dependents, if non-empty, used to sort the chunks.
* @param numCompiles The number of chunks.
* @param sourcesPerCompile The number of sources per chunk.
* @return
*/
CompileChunk[] createCompileChunks(Map<String,Set<URI>> pkgSrcs,
Map<String,Set<String>> oldPackageDependents,
int numCompiles,
int sourcesPerCompile) {
CompileChunk[] compileChunks = new CompileChunk[numCompiles];
for (int i=0; i<compileChunks.length; ++i) {
compileChunks[i] = new CompileChunk();
}
// Now go through the packages and spread out the source on the different chunks.
int ci = 0;
// Sort the packages
String[] packageNames = pkgSrcs.keySet().toArray(new String[0]);
Arrays.sort(packageNames);
String from = null;
for (String pkgName : packageNames) {
CompileChunk cc = compileChunks[ci];
Set<URI> s = pkgSrcs.get(pkgName);
if (cc.srcs.size()+s.size() > sourcesPerCompile && ci < numCompiles-1) {
from = null;
ci++;
cc = compileChunks[ci];
}
cc.numPackages++;
cc.srcs.addAll(s);
// Calculate nice package names to use as information when compiling.
String justPkgName = Util.justPackageName(pkgName);
// Fetch how many packages depend on this package from the old build state.
Set<String> ss = oldPackageDependents.get(pkgName);
if (ss != null) {
// Accumulate this information onto this chunk.
cc.numDependents += ss.size();
}
if (from == null || from.trim().equals("")) from = justPkgName;
cc.pkgNames.append(justPkgName+"("+s.size()+") ");
cc.pkgFromTos = from+" to "+justPkgName;
}
// If we are compiling serially, sort the chunks, so that the chunk (with the most dependents) (usually the chunk
// containing java.lang.Object, is to be compiled first!
// For concurrent compilation, this does not matter.
Arrays.sort(compileChunks);
return compileChunks;
}
}

View File

@ -0,0 +1,221 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.io.*;
import java.net.URI;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
/**
* Compile properties transform a properties file into a Java source file.
* Java has built in support for reading properties from either a text file
* in the source or a compiled java source file.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class CompileProperties implements Transformer
{
// Any extra information passed from the command line, for example if:
// -tr .proppp=com.sun.tools.javac.smart.CompileProperties,sun.util.resources.LocaleNamesBundle
// then extra will be "sun.util.resources.LocaleNamesBundle"
String extra;
public void setExtra(String e) {
extra = e;
}
public void setExtra(String[] a) {
}
public boolean transform(Map<String,Set<URI>> pkgSrcs,
Set<URI> visibleSrcs,
Map<URI,Set<String>> visibleClasses,
Map<String,Set<String>> oldPackageDependents,
URI destRoot,
Map<String,Set<URI>> packageArtifacts,
Map<String,Set<String>> packageDependencies,
Map<String,String> packagePublicApis,
int debugLevel,
boolean incremental,
int numCores,
PrintStream out,
PrintStream err) {
boolean rc = true;
for (String pkgName : pkgSrcs.keySet()) {
String pkgNameF = Util.toFileSystemPath(pkgName);
for (URI u : pkgSrcs.get(pkgName)) {
File src = new File(u);
boolean r = compile(pkgName, pkgNameF, src, new File(destRoot), debugLevel,
packageArtifacts);
if (r == false) {
rc = false;
}
}
}
return rc;
}
boolean compile(String pkgName, String pkgNameF, File src, File destRoot, int debugLevel,
Map<String,Set<URI>> packageArtifacts)
{
String superClass = "java.util.ListResourceBundle";
if (extra != null) {
superClass = extra;
}
// Load the properties file.
Properties p = new Properties();
try {
p.load(new FileInputStream(src));
} catch (IOException e) {
Log.error("Error reading file "+src.getPath());
return false;
}
// Calculate the name of the Java source file to be generated.
int dp = src.getName().lastIndexOf(".");
String classname = src.getName().substring(0,dp);
// Sort the properties in increasing key order.
List<String> sortedKeys = new ArrayList<String>();
for (Object key : p.keySet()) {
sortedKeys.add((String)key);
}
Collections.sort(sortedKeys);
Iterator<String> keys = sortedKeys.iterator();
// Collect the properties into a string buffer.
StringBuilder data = new StringBuilder();
while (keys.hasNext()) {
String key = keys.next();
data.append(" { \"" + escape(key) + "\", \"" +
escape((String)p.get(key)) + "\" },\n");
}
// Create dest file name. It is derived from the properties file name.
String destFilename = destRoot.getPath()+File.separator+pkgNameF+File.separator+classname+".java";
File dest = new File(destFilename);
// Make sure the dest directories exist.
if (!dest.getParentFile().isDirectory()) {
if (!dest.getParentFile().mkdirs()) {
Log.error("Could not create the directory "+dest.getParentFile().getPath());
return false;
}
}
Set<URI> as = packageArtifacts.get(pkgName);
if (as == null) {
as = new HashSet<URI>();
packageArtifacts.put(pkgName, as);
}
as.add(dest.toURI());
if (dest.exists() && dest.lastModified() > src.lastModified()) {
// A generated file exists, and its timestamp is newer than the source.
// Assume that we do not need to regenerate the dest file!
// Thus we are done.
return true;
}
String packageString = "package " + pkgNameF.replace(File.separatorChar,'.') + ";\n\n";
Log.info("Compiling property file "+pkgNameF+File.separator+src.getName());
try (Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(dest)))) {
MessageFormat format = new MessageFormat(FORMAT);
writer.write(format.format(new Object[] { packageString, classname, superClass, data }));
} catch ( IOException e ) {
Log.error("Could not write file "+dest.getPath());
return false;
}
return true;
}
private static final String FORMAT =
"{0}" +
"public final class {1} extends {2} '{'\n" +
" protected final Object[][] getContents() '{'\n" +
" return new Object[][] '{'\n" +
"{3}" +
" };\n" +
" }\n" +
"}\n";
public static String escape(String theString) {
int len = theString.length();
StringBuilder outBuffer = new StringBuilder(len*2);
for(int x=0; x<len; x++) {
char aChar = theString.charAt(x);
switch(aChar) {
case '\\':outBuffer.append('\\'); outBuffer.append('\\');
break;
case '\t':outBuffer.append('\\'); outBuffer.append('t');
break;
case '\n':outBuffer.append('\\'); outBuffer.append('n');
break;
case '\r':outBuffer.append('\\'); outBuffer.append('r');
break;
case '\f':outBuffer.append('\\'); outBuffer.append('f');
break;
default:
if ((aChar < 0x0020) || (aChar > 0x007e)) {
outBuffer.append('\\');
outBuffer.append('u');
outBuffer.append(toHex((aChar >> 12) & 0xF));
outBuffer.append(toHex((aChar >> 8) & 0xF));
outBuffer.append(toHex((aChar >> 4) & 0xF));
outBuffer.append(toHex( aChar & 0xF));
} else {
if (aChar == '"') {
outBuffer.append('\\');
}
outBuffer.append(aChar);
}
}
}
return outBuffer.toString();
}
private static char toHex(int nibble) {
return hexDigit[(nibble & 0xF)];
}
private static final char[] hexDigit = {
'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
};
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.io.*;
import java.net.URI;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
/**
* The copy file transform simply copies a matching file from -src to -d .
* Such files are typically images, xml documents and other data files.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class CopyFile implements Transformer {
public void setExtra(String e) {
}
public void setExtra(String[] a) {
}
public boolean transform(Map<String,Set<URI>> pkgSrcs,
Set<URI> visibleSrcs,
Map<URI,Set<String>> visibleClasses,
Map<String,Set<String>> oldPackageDependents,
URI destRoot,
Map<String,Set<URI>> packageArtifacts,
Map<String,Set<String>> packageDependencies,
Map<String,String> packagePubapis,
int debugLevel,
boolean incremental,
int numCores,
PrintStream out,
PrintStream err)
{
boolean rc = true;
String dest_filename;
File dest;
for (String pkgName : pkgSrcs.keySet()) {
String pkgNameF = Util.toFileSystemPath(pkgName);
for (URI u : pkgSrcs.get(pkgName)) {
File src = new File(u);
File destDir;
destDir = new File(destRoot.getPath()+File.separator+pkgNameF);
dest_filename = destRoot.getPath()+File.separator+pkgNameF+File.separator+src.getName();
dest = new File(dest_filename);
if (!destDir.isDirectory()) {
if (!destDir.mkdirs()) {
Log.error("Error: The copier could not create the directory "+
destDir.getPath());
return false;
}
}
Set<URI> as = packageArtifacts.get(pkgName);
if (as == null) {
as = new HashSet<URI>();
packageArtifacts.put(pkgName, as);
}
as.add(dest.toURI());
if (dest.exists() && dest.lastModified() > src.lastModified()) {
// A copied file exists, and its timestamp is newer than the source.
continue;
}
Log.info("Copying "+pkgNameF+File.separator+src.getName());
try (InputStream fin = new FileInputStream(src);
OutputStream fout = new FileOutputStream(dest)) {
byte[] buf = new byte[1024];
int len;
while ((len = fin.read(buf)) > 0){
fout.write(buf, 0, len);
}
}
catch(IOException e){
Log.error("Could not copy the file "+src.getPath()+" to "+dest.getPath());
rc = false;
}
}
}
return rc;
}
}

View File

@ -0,0 +1,857 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.io.*;
import java.util.Collections;
import java.util.Date;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.text.SimpleDateFormat;
import java.net.URI;
import java.util.*;
/**
* The javac state class maintains the previous (prev) and the current (now)
* build states and everything else that goes into the javac_state file.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class JavacState
{
// The arguments to the compile. If not identical, then it cannot
// be an incremental build!
String theArgs;
// The number of cores limits how many threads are used for heavy concurrent work.
int numCores;
// The bin_dir/javac_state
private String javacStateFilename;
private File javacState;
// The previous build state is loaded from javac_state
private BuildState prev;
// The current build state is constructed during the build,
// then saved as the new javac_state.
private BuildState now;
// Something has changed in the javac_state. It needs to be saved!
private boolean needsSaving;
// If this is a new javac_state file, then do not print unnecessary messages.
private boolean newJavacState;
// These are packages where something has changed and the package
// needs to be recompiled. Actions that trigger recompilation:
// * source belonging to the package has changed
// * artifact belonging to the package is lost, or its timestamp has been changed.
// * an unknown artifact has appeared, we simply delete it, but we also trigger a recompilation.
// * a package that is tainted, taints all packages that depend on it.
private Set<String> taintedPackages;
// After a compile, the pubapis are compared with the pubapis stored in the javac state file.
// Any packages where the pubapi differ are added to this set.
// Later we use this set and the dependency information to taint dependent packages.
private Set<String> packagesWithChangedPublicApis;
// When a module-info.java file is changed, taint the module,
// then taint all modules that depend on that that module.
// A module dependency can occur directly through a require, or
// indirectly through a module that does a public export for the first tainted module.
// When all modules are tainted, then taint all packages belonging to these modules.
// Then rebuild. It is perhaps possible (and valuable?) to do a more finegrained examination of the
// change in module-info.java, but that will have to wait.
private Set<String> taintedModules;
// The set of all packages that has been recompiled.
// Copy over the javac_state for the packages that did not need recompilation,
// verbatim from the previous (prev) to the new (now) build state.
private Set<String> recompiledPackages;
// The output directories filled with tasty artifacts.
private File binDir, gensrcDir, headerDir;
// The current status of the file system.
private Set<File> binArtifacts;
private Set<File> gensrcArtifacts;
private Set<File> headerArtifacts;
// The status of the sources.
Set<Source> removedSources = null;
Set<Source> addedSources = null;
Set<Source> modifiedSources = null;
// Visible sources for linking. These are the only
// ones that -sourcepath is allowed to see.
Set<URI> visibleSrcs;
// Visible classes for linking. These are the only
// ones that -classpath is allowed to see.
// It maps from a classpath root to the set of visible classes for that root.
// If the set is empty, then all classes are visible for that root.
// It can also map from a jar file to the set of visible classes for that jar file.
Map<URI,Set<String>> visibleClasses;
// Setup two transforms that always exist.
private CopyFile copyFiles = new CopyFile();
private CompileJavaPackages compileJavaPackages = new CompileJavaPackages();
// Where to send stdout and stderr.
private PrintStream out, err;
JavacState(String[] args, File bd, File gd, File hd, boolean permitUnidentifiedArtifacts, boolean removeJavacState,
PrintStream o, PrintStream e) {
out = o;
err = e;
numCores = Main.findNumberOption(args, "-j");
theArgs = "";
for (String a : removeArgsNotAffectingState(args)) {
theArgs = theArgs+a+" ";
}
binDir = bd;
gensrcDir = gd;
headerDir = hd;
javacStateFilename = binDir.getPath()+File.separator+"javac_state";
javacState = new File(javacStateFilename);
if (removeJavacState && javacState.exists()) {
javacState.delete();
}
newJavacState = false;
if (!javacState.exists()) {
newJavacState = true;
// If there is no javac_state then delete the contents of all the artifact dirs!
// We do not want to risk building a broken incremental build.
// BUT since the makefiles still copy things straight into the bin_dir et al,
// we avoid deleting files here, if the option --permit-unidentified-classes was supplied.
if (!permitUnidentifiedArtifacts) {
deleteContents(binDir);
deleteContents(gensrcDir);
deleteContents(headerDir);
}
needsSaving = true;
}
prev = new BuildState();
now = new BuildState();
taintedPackages = new HashSet<String>();
recompiledPackages = new HashSet<String>();
packagesWithChangedPublicApis = new HashSet<String>();
}
public BuildState prev() { return prev; }
public BuildState now() { return now; }
/**
* Remove args not affecting the state.
*/
static String[] removeArgsNotAffectingState(String[] args) {
String[] out = new String[args.length];
int j = 0;
for (int i = 0; i<args.length; ++i) {
if (args[i].equals("-j")) {
// Just skip it and skip following value
i++;
} else if (args[i].startsWith("--server:")) {
// Just skip it.
} else if (args[i].startsWith("--log=")) {
// Just skip it.
} else if (args[i].equals("--compare-found-sources")) {
// Just skip it and skip verify file name
i++;
} else {
// Copy argument.
out[j] = args[i];
j++;
}
}
String[] ret = new String[j];
System.arraycopy(out, 0, ret, 0, j);
return ret;
}
/**
* Specify which sources are visible to the compiler through -sourcepath.
*/
public void setVisibleSources(Map<String,Source> vs) {
visibleSrcs = new HashSet<URI>();
for (String s : vs.keySet()) {
Source src = vs.get(s);
visibleSrcs.add(src.file().toURI());
}
}
/**
* Specify which classes are visible to the compiler through -classpath.
*/
public void setVisibleClasses(Map<String,Source> vs) {
visibleSrcs = new HashSet<URI>();
for (String s : vs.keySet()) {
Source src = vs.get(s);
visibleSrcs.add(src.file().toURI());
}
}
/**
* Returns true if this is an incremental build.
*/
public boolean isIncremental() {
return !prev.sources().isEmpty();
}
/**
* Find all artifacts that exists on disk.
*/
public void findAllArtifacts() {
binArtifacts = findAllFiles(binDir);
gensrcArtifacts = findAllFiles(gensrcDir);
headerArtifacts = findAllFiles(headerDir);
}
/**
* Lookup the artifacts generated for this package in the previous build.
*/
private Map<String,File> fetchPrevArtifacts(String pkg) {
Package p = prev.packages().get(pkg);
if (p != null) {
return p.artifacts();
}
return new HashMap<String,File>();
}
/**
* Delete all prev artifacts in the currently tainted packages.
*/
public void deleteClassArtifactsInTaintedPackages() {
for (String pkg : taintedPackages) {
Map<String,File> arts = fetchPrevArtifacts(pkg);
for (File f : arts.values()) {
if (f.exists() && f.getName().endsWith(".class")) {
f.delete();
}
}
}
}
/**
* Mark the javac_state file to be in need of saving and as a side effect,
* it gets a new timestamp.
*/
private void needsSaving() {
needsSaving = true;
}
/**
* Save the javac_state file.
*/
public void save() throws IOException {
if (!needsSaving) return;
try (FileWriter out = new FileWriter(javacStateFilename)) {
StringBuilder b = new StringBuilder();
long millisNow = System.currentTimeMillis();
Date d = new Date(millisNow);
SimpleDateFormat df =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
b.append("# javac_state ver 0.3 generated "+millisNow+" "+df.format(d)+"\n");
b.append("# This format might change at any time. Please do not depend on it.\n");
b.append("# M module\n");
b.append("# P package\n");
b.append("# S C source_tobe_compiled timestamp\n");
b.append("# S L link_only_source timestamp\n");
b.append("# G C generated_source timestamp\n");
b.append("# A artifact timestamp\n");
b.append("# D dependency\n");
b.append("# I pubapi\n");
b.append("# R arguments\n");
b.append("R ").append(theArgs).append("\n");
// Copy over the javac_state for the packages that did not need recompilation.
now.copyPackagesExcept(prev, recompiledPackages, new HashSet<String>());
// Save the packages, ie package names, dependencies, pubapis and artifacts!
// I.e. the lot.
Module.saveModules(now.modules(), b);
String s = b.toString();
out.write(s, 0, s.length());
}
}
/**
* Load a javac_state file.
*/
public static JavacState load(String[] args, File binDir, File gensrcDir, File headerDir,
boolean permitUnidentifiedArtifacts, PrintStream out, PrintStream err) {
JavacState db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, false, out, err);
Module lastModule = null;
Package lastPackage = null;
Source lastSource = null;
boolean noFileFound = false;
boolean foundCorrectVerNr = false;
boolean newCommandLine = false;
boolean syntaxError = false;
try (BufferedReader in = new BufferedReader(new FileReader(db.javacStateFilename))) {
for (;;) {
String l = in.readLine();
if (l==null) break;
if (l.length()>=3 && l.charAt(1) == ' ') {
char c = l.charAt(0);
if (c == 'M') {
lastModule = db.prev.loadModule(l);
} else
if (c == 'P') {
if (lastModule == null) { syntaxError = true; break; }
lastPackage = db.prev.loadPackage(lastModule, l);
} else
if (c == 'D') {
if (lastModule == null || lastPackage == null) { syntaxError = true; break; }
lastPackage.loadDependency(l);
} else
if (c == 'I') {
if (lastModule == null || lastPackage == null) { syntaxError = true; break; }
lastPackage.loadPubapi(l);
} else
if (c == 'A') {
if (lastModule == null || lastPackage == null) { syntaxError = true; break; }
lastPackage.loadArtifact(l);
} else
if (c == 'S') {
if (lastModule == null || lastPackage == null) { syntaxError = true; break; }
lastSource = db.prev.loadSource(lastPackage, l, false);
} else
if (c == 'G') {
if (lastModule == null || lastPackage == null) { syntaxError = true; break; }
lastSource = db.prev.loadSource(lastPackage, l, true);
} else
if (c == 'R') {
String ncmdl = "R "+db.theArgs;
if (!l.equals(ncmdl)) {
newCommandLine = true;
}
} else
if (c == '#') {
if (l.startsWith("# javac_state ver ")) {
int sp = l.indexOf(" ", 18);
if (sp != -1) {
String ver = l.substring(18,sp);
if (!ver.equals("0.3")) {
break;
}
foundCorrectVerNr = true;
}
}
}
}
}
} catch (FileNotFoundException e) {
// Silently create a new javac_state file.
noFileFound = true;
} catch (IOException e) {
Log.info("Dropping old javac_state because of errors when reading it.");
db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, true, out, err);
foundCorrectVerNr = true;
newCommandLine = false;
syntaxError = false;
}
if (foundCorrectVerNr == false && !noFileFound) {
Log.info("Dropping old javac_state since it is of an old version.");
db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, true, out, err);
} else
if (newCommandLine == true && !noFileFound) {
Log.info("Dropping old javac_state since a new command line is used!");
db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, true, out, err);
} else
if (syntaxError == true) {
Log.info("Dropping old javac_state since it contains syntax errors.");
db = new JavacState(args, binDir, gensrcDir, headerDir, permitUnidentifiedArtifacts, true, out, err);
}
db.prev.calculateDependents();
return db;
}
/**
* Mark a java package as tainted, ie it needs recompilation.
*/
public void taintPackage(String name, String because) {
if (!taintedPackages.contains(name)) {
if (because != null) Log.debug("Tainting "+Util.justPackageName(name)+" because "+because);
// It has not been tainted before.
taintedPackages.add(name);
needsSaving();
Package nowp = now.packages().get(name);
if (nowp != null) {
for (String d : nowp.dependents()) {
taintPackage(d, because);
}
}
}
}
/**
* This packages need recompilation.
*/
public Set<String> taintedPackages() {
return taintedPackages;
}
/**
* Clean out the tainted package set, used after the first round of compiles,
* prior to propagating dependencies.
*/
public void clearTaintedPackages() {
taintedPackages = new HashSet<String>();
}
/**
* Go through all sources and check which have been removed, added or modified
* and taint the corresponding packages.
*/
public void checkSourceStatus(boolean check_gensrc) {
removedSources = calculateRemovedSources();
for (Source s : removedSources) {
if (!s.isGenerated() || check_gensrc) {
taintPackage(s.pkg().name(), "source "+s.name()+" was removed");
}
}
addedSources = calculateAddedSources();
for (Source s : addedSources) {
String msg = null;
if (isIncremental()) {
// When building from scratch, there is no point
// printing "was added" for every file since all files are added.
// However for an incremental build it makes sense.
msg = "source "+s.name()+" was added";
}
if (!s.isGenerated() || check_gensrc) {
taintPackage(s.pkg().name(), msg);
}
}
modifiedSources = calculateModifiedSources();
for (Source s : modifiedSources) {
if (!s.isGenerated() || check_gensrc) {
taintPackage(s.pkg().name(), "source "+s.name()+" was modified");
}
}
}
/**
* Acquire the compile_java_packages suffix rule for .java files.
*/
public Map<String,Transformer> getJavaSuffixRule() {
Map<String,Transformer> sr = new HashMap<String,Transformer>();
sr.put(".java", compileJavaPackages);
return sr;
}
/**
* Acquire the copying transform.
*/
public Transformer getCopier() {
return copyFiles;
}
/**
* If artifacts have gone missing, force a recompile of the packages
* they belong to.
*/
public void taintPackagesThatMissArtifacts() {
for (Package pkg : prev.packages().values()) {
for (File f : pkg.artifacts().values()) {
if (!f.exists()) {
// Hmm, the artifact on disk does not exist! Someone has removed it....
// Lets rebuild the package.
taintPackage(pkg.name(), ""+f+" is missing.");
}
}
}
}
/**
* Propagate recompilation through the dependency chains.
* Avoid re-tainting packages that have already been compiled.
*/
public void taintPackagesDependingOnChangedPackages(Set<String> pkgs, Set<String> recentlyCompiled) {
for (Package pkg : prev.packages().values()) {
for (String dep : pkg.dependencies()) {
if (pkgs.contains(dep) && !recentlyCompiled.contains(pkg.name())) {
taintPackage(pkg.name(), " its depending on "+dep);
}
}
}
}
/**
* Scan all output dirs for artifacts and remove those files (artifacts?)
* that are not recognized as such, in the javac_state file.
*/
public void removeUnidentifiedArtifacts() {
Set<File> allKnownArtifacts = new HashSet<File>();
for (Package pkg : prev.packages().values()) {
for (File f : pkg.artifacts().values()) {
allKnownArtifacts.add(f);
}
}
// Do not forget about javac_state....
allKnownArtifacts.add(javacState);
for (File f : binArtifacts) {
if (!allKnownArtifacts.contains(f)) {
Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state.");
f.delete();
}
}
for (File f : headerArtifacts) {
if (!allKnownArtifacts.contains(f)) {
Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state.");
f.delete();
}
}
for (File f : gensrcArtifacts) {
if (!allKnownArtifacts.contains(f)) {
Log.debug("Removing "+f.getPath()+" since it is unknown to the javac_state.");
f.delete();
}
}
}
/**
* Remove artifacts that are no longer produced when compiling!
*/
public void removeSuperfluousArtifacts(Set<String> recentlyCompiled) {
// Nothing to do, if nothing was recompiled.
if (recentlyCompiled.size() == 0) return;
for (String pkg : now.packages().keySet()) {
// If this package has not been recompiled, skip the check.
if (!recentlyCompiled.contains(pkg)) continue;
Collection<File> arts = now.artifacts().values();
for (File f : fetchPrevArtifacts(pkg).values()) {
if (!arts.contains(f)) {
Log.debug("Removing "+f.getPath()+" since it is now superfluous!");
if (f.exists()) f.delete();
}
}
}
}
/**
* Return those files belonging to prev, but not now.
*/
private Set<Source> calculateRemovedSources() {
Set<Source> removed = new HashSet<Source>();
for (String src : prev.sources().keySet()) {
if (now.sources().get(src) == null) {
removed.add(prev.sources().get(src));
}
}
return removed;
}
/**
* Return those files belonging to now, but not prev.
*/
private Set<Source> calculateAddedSources() {
Set<Source> added = new HashSet<Source>();
for (String src : now.sources().keySet()) {
if (prev.sources().get(src) == null) {
added.add(now.sources().get(src));
}
}
return added;
}
/**
* Return those files where the timestamp is newer.
* If a source file timestamp suddenly is older than what is known
* about it in javac_state, then consider it modified, but print
* a warning!
*/
private Set<Source> calculateModifiedSources() {
Set<Source> modified = new HashSet<Source>();
for (String src : now.sources().keySet()) {
Source n = now.sources().get(src);
Source t = prev.sources().get(src);
if (prev.sources().get(src) != null) {
if (t != null) {
if (n.lastModified() > t.lastModified()) {
modified.add(n);
} else if (n.lastModified() < t.lastModified()) {
modified.add(n);
Log.warn("The source file "+n.name()+" timestamp has moved backwards in time.");
}
}
}
}
return modified;
}
/**
* Recursively delete a directory and all its contents.
*/
private static void deleteContents(File dir) {
if (dir != null && dir.exists()) {
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
deleteContents(f);
}
f.delete();
}
}
}
/**
* Run the copy translator only.
*/
public void performCopying(File binDir, Map<String,Transformer> suffixRules) {
Map<String,Transformer> sr = new HashMap<String,Transformer>();
for (Map.Entry<String,Transformer> e : suffixRules.entrySet()) {
if (e.getValue() == copyFiles) {
sr.put(e.getKey(), e.getValue());
}
}
perform(binDir, sr);
}
/**
* Run all the translators that translate into java source code.
* I.e. all translators that are not copy nor compile_java_source.
*/
public void performTranslation(File gensrcDir, Map<String,Transformer> suffixRules) {
Map<String,Transformer> sr = new HashMap<String,Transformer>();
for (Map.Entry<String,Transformer> e : suffixRules.entrySet()) {
if (e.getValue() != copyFiles &&
e.getValue() != compileJavaPackages) {
sr.put(e.getKey(), e.getValue());
}
}
perform(gensrcDir, sr);
}
/**
* Compile all the java sources. Return true, if it needs to be called again!
*/
public boolean performJavaCompilations(File binDir,
String serverSettings,
String[] args,
Set<String> recentlyCompiled,
boolean[] rcValue) {
Map<String,Transformer> suffixRules = new HashMap<String,Transformer>();
suffixRules.put(".java", compileJavaPackages);
compileJavaPackages.setExtra(serverSettings);
compileJavaPackages.setExtra(args);
rcValue[0] = perform(binDir, suffixRules);
recentlyCompiled.addAll(taintedPackages());
clearTaintedPackages();
boolean again = !packagesWithChangedPublicApis.isEmpty();
taintPackagesDependingOnChangedPackages(packagesWithChangedPublicApis, recentlyCompiled);
packagesWithChangedPublicApis = new HashSet<String>();
return again && rcValue[0];
}
/**
* Store the source into the set of sources belonging to the given transform.
*/
private void addFileToTransform(Map<Transformer,Map<String,Set<URI>>> gs, Transformer t, Source s) {
Map<String,Set<URI>> fs = gs.get(t);
if (fs == null) {
fs = new HashMap<String,Set<URI>>();
gs.put(t, fs);
}
Set<URI> ss = fs.get(s.pkg().name());
if (ss == null) {
ss = new HashSet<URI>();
fs.put(s.pkg().name(), ss);
}
ss.add(s.file().toURI());
}
/**
* For all packages, find all sources belonging to the package, group the sources
* based on their transformers and apply the transformers on each source code group.
*/
private boolean perform(File outputDir, Map<String,Transformer> suffixRules)
{
boolean rc = true;
// Group sources based on transforms. A source file can only belong to a single transform.
Map<Transformer,Map<String,Set<URI>>> groupedSources = new HashMap<Transformer,Map<String,Set<URI>>>();
for (Source src : now.sources().values()) {
Transformer t = suffixRules.get(src.suffix());
if (t != null) {
if (taintedPackages.contains(src.pkg().name()) && !src.isLinkedOnly()) {
addFileToTransform(groupedSources, t, src);
}
}
}
// Go through the transforms and transform them.
for (Map.Entry<Transformer,Map<String,Set<URI>>> e : groupedSources.entrySet()) {
Transformer t = e.getKey();
Map<String,Set<URI>> srcs = e.getValue();
// These maps need to be synchronized since multiple threads will be writing results into them.
Map<String,Set<URI>> packageArtifacts = Collections.synchronizedMap(new HashMap<String,Set<URI>>());
Map<String,Set<String>> packageDependencies = Collections.synchronizedMap(new HashMap<String,Set<String>>());
Map<String,String> packagePublicApis = Collections.synchronizedMap(new HashMap<String,String>());
boolean r = t.transform(srcs,
visibleSrcs,
visibleClasses,
prev.dependents(),
outputDir.toURI(),
packageArtifacts,
packageDependencies,
packagePublicApis,
0,
isIncremental(),
numCores,
out,
err);
if (!r) rc = false;
for (String p : srcs.keySet()) {
recompiledPackages.add(p);
}
// The transform is done! Extract all the artifacts and store the info into the Package objects.
for (Map.Entry<String,Set<URI>> a : packageArtifacts.entrySet()) {
Module mnow = now.findModuleFromPackageName(a.getKey());
mnow.addArtifacts(a.getKey(), a.getValue());
}
// Extract all the dependencies and store the info into the Package objects.
for (Map.Entry<String,Set<String>> a : packageDependencies.entrySet()) {
Set<String> deps = a.getValue();
Module mnow = now.findModuleFromPackageName(a.getKey());
mnow.setDependencies(a.getKey(), deps);
}
// Extract all the pubapis and store the info into the Package objects.
for (Map.Entry<String,String> a : packagePublicApis.entrySet()) {
Module mprev = prev.findModuleFromPackageName(a.getKey());
List<String> pubapi = Package.pubapiToList(a.getValue());
Module mnow = now.findModuleFromPackageName(a.getKey());
mnow.setPubapi(a.getKey(), pubapi);
if (mprev.hasPubapiChanged(a.getKey(), pubapi)) {
// Aha! The pubapi of this package has changed!
// It can also be a new compile from scratch.
if (mprev.lookupPackage(a.getKey()).existsInJavacState()) {
// This is an incremental compile! The pubapi
// did change. Trigger recompilation of dependents.
packagesWithChangedPublicApis.add(a.getKey());
Log.info("The pubapi of "+Util.justPackageName(a.getKey())+" has changed!");
}
}
}
}
return rc;
}
/**
* Utility method to recursively find all files below a directory.
*/
private static Set<File> findAllFiles(File dir) {
Set<File> foundFiles = new HashSet<File>();
if (dir == null) {
return foundFiles;
}
recurse(dir, foundFiles);
return foundFiles;
}
private static void recurse(File dir, Set<File> foundFiles) {
for (File f : dir.listFiles()) {
if (f.isFile()) {
foundFiles.add(f);
} else if (f.isDirectory()) {
recurse(f, foundFiles);
}
}
}
/**
* Compare the calculate source list, with an explicit list, usually supplied from the makefile.
* Used to detect bugs where the makefile and sjavac have different opinions on which files
* should be compiled.
*/
public void compareWithMakefileList(File makefileSourceList)
throws ProblemException
{
// If we are building on win32 using for example cygwin the paths in the makefile source list
// might be /cygdrive/c/.... which does not match c:\....
// We need to adjust our calculated sources to be identical, if necessary.
boolean mightNeedRewriting = File.pathSeparatorChar == ';';
if (makefileSourceList == null) return;
Set<String> calculatedSources = new HashSet<String>();
Set<String> listedSources = new HashSet<String>();
// Create a set of filenames with full paths.
for (Source s : now.sources().values()) {
calculatedSources.add(s.file().getPath());
}
// Read in the file and create another set of filenames with full paths.
try {
BufferedReader in = new BufferedReader(new FileReader(makefileSourceList));
for (;;) {
String l = in.readLine();
if (l==null) break;
l = l.trim();
if (mightNeedRewriting) {
if (l.indexOf(":") == 1 && l.indexOf("\\") == 2) {
// Everything a-ok, the format is already C:\foo\bar
} else if (l.indexOf(":") == 1 && l.indexOf("/") == 2) {
// The format is C:/foo/bar, rewrite into the above format.
l = l.replaceAll("/","\\\\");
} else if (l.charAt(0) == '/' && l.indexOf("/",1) != -1) {
// The format might be: /cygdrive/c/foo/bar, rewrite into the above format.
// Do not hardcode the name cygdrive here.
int slash = l.indexOf("/",1);
l = l.replaceAll("/","\\\\");
l = ""+l.charAt(slash+1)+":"+l.substring(slash+2);
}
if (Character.isLowerCase(l.charAt(0))) {
l = Character.toUpperCase(l.charAt(0))+l.substring(1);
}
}
listedSources.add(l);
}
} catch (FileNotFoundException e) {
throw new ProblemException("Could not open "+makefileSourceList.getPath()+" since it does not exist!");
} catch (IOException e) {
throw new ProblemException("Could not read "+makefileSourceList.getPath());
}
for (String s : listedSources) {
if (!calculatedSources.contains(s)) {
throw new ProblemException("The makefile listed source "+s+" was not calculated by the smart javac wrapper!");
}
}
for (String s : calculatedSources) {
if (!listedSources.contains(s)) {
throw new ProblemException("The smart javac wrapper calculated source "+s+" was not listed by the makefiles!");
}
}
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.io.PrintStream;
/**
* Utility class only for sjavac logging.
* The log level can be set using for example --log=DEBUG on the sjavac command line.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class Log {
private static PrintStream out, err;
public final static int WARN = 1;
public final static int INFO = 2;
public final static int DEBUG = 3;
public final static int TRACE = 4;
private static int level = WARN;
static public void trace(String msg) {
if (level >= TRACE) {
out.println(msg);
}
}
static public void debug(String msg) {
if (level >= DEBUG) {
out.println(msg);
}
}
static public void info(String msg) {
if (level >= INFO) {
out.println(msg);
}
}
static public void warn(String msg) {
err.println(msg);
}
static public void error(String msg) {
err.println(msg);
}
static public void setLogLevel(String l, PrintStream o, PrintStream e)
throws ProblemException {
out = o;
err = e;
if (l.equals("warn")) level = WARN;
else if (l.equals("info")) level = INFO;
else if (l.equals("debug")) level = DEBUG;
else if (l.equals("trace")) level = TRACE;
else throw new ProblemException("No such log level \""+l+"\"");
}
static public boolean isTracing() {
return level >= TRACE;
}
static public boolean isDebugging() {
return level >= DEBUG;
}
}

View File

@ -0,0 +1,969 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.sun.tools.sjavac.server.JavacServer;
import java.io.IOException;
import java.io.PrintStream;
import java.util.*;
/**
* The main class of the smart javac wrapper tool.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class Main {
/* This is a smart javac wrapper primarily used when building the OpenJDK,
though other projects are welcome to use it too. But please be aware
that it is not an official api and will change in the future.
(We really mean it!)
Goals:
** Create a state file, containing information about the build, so
that incremental builds only rebuild what is necessary. Also the
state file can be used by make/ant to detect when to trigger
a call to the smart javac wrapper.
This file is called bin/javac_state (assuming that you specified "-d bin")
Thus the simplest makefile is:
SJAVAC=java -cp .../tools.jar com.sun.tools.sjavac.Main
SRCS=$(shell find src -name "*.java")
bin/javac_state : $(SRCS)
$(SJAVAC) src -d bin
This makefile will run very fast and detect properly when Java code needs to
be recompiled. The smart javac wrapper will then use the information in java_state
to do an efficient incremental compile.
Previously it was near enough impossible to write an efficient makefile for Java
with support for incremental builds and dependency tracking.
** Separate java sources to be compiled from java
sources used >only< for linking. The options:
"dir" points to root dir with sources to be compiled
"-sourcepath dir" points to root dir with sources used only for linking
"-classpath dir" points to dir with classes used only for linking (as before)
** Use all cores for compilation by default.
"-j 4" limit the number of cores to 4.
For the moment, the sjavac server additionally limits the number of cores to three.
This will improve in the future when more sharing is performed between concurrent JavaCompilers.
** Basic translation support from other sources to java, and then compilation of the generated java.
This functionality might be moved into annotation processors instead.
Again this is driven by the OpenJDK sources where properties and a few other types of files
are converted into Java sources regularily. The javac_state embraces copy and tr, and perform
incremental recompiles and copying for these as well. META-INF will be a special copy rule
that will copy any files found below any META-INF dir in src to the bin/META-INF dir.
"-copy .gif"
"-copy META-INF"
"-tr .prop=com.sun.tools.javac.smart.CompileProperties
"-tr .propp=com.sun.tools.javac.smart.CompileProperties,java.util.ListResourceBundle
"-tr .proppp=com.sun.tools.javac.smart.CompileProperties,sun.util.resources.LocaleNamesBundle
** Control which classes in the src,sourcepath and classpath that javac is allowed to see.
Again, this is necessary to deal with the source code structure of the OpenJDK which is
intricate (read messy).
"-i tools.*" to include the tools package and all its subpackages in the build.
"-x tools.net.aviancarrier.*" to exclude the aviancarrier package and all its sources and subpackages.
"-x tools.net.drums" to exclude the drums package only, keep its subpackages.
"-xf tools/net/Bar.java" // Do not compile this file...
"-xf *Bor.java" // Do not compile Bor.java wherever it is found, BUT do compile ABor.java!
"-if tools/net/Bor.java" // Only compile this file...odd, but sometimes used.
** The smart javac wrapper is driven by the modification time on the source files and compared
to the modification times written into the javac_state file.
It does not compare the modification time of the source with the modification time of the artifact.
However it will detect if the modification time of an artifact has changed compared to the java_state,
and this will trigger a delete of the artifact and a subsequent recompile of the source.
The smart javac wrapper is not a generic makefile/ant system. Its purpose is to compile java source
as the final step before the output dir is finalized and immediately jared, or jmodded. The output
dir should be considered opaque. Do not write into the outputdir yourself!
Any artifacts found in the outputdir that javac_state does not know of, will be deleted!
This can however be prevented, using the switch --permit-unidentified-artifacts
This switch is necessary when build the OpenJDK because its makefiles still write directly to
the output classes dirs.
Any makefile/ant rules that want to put contents into the outputdir should put the content
in one of several source roots. Static content that is under version control, can be put in the same source
code tree as the Java sources. Dynamic content that is generated by make/ant on the fly, should
be put in a separate gensrc_stuff root. The smart javac wrapper call will then take the arguments:
"gensrc_stuff src -d bin"
The command line:
java -cp tools.jar com.sun.tools.sjavac.Main \
-i "com.bar.*" -x "com.bar.foo.*" \
first_root \
-i "com.bar.foo.*" \
second_root \
-x "org.net.*" \
-sourcepath link_root_sources \
-classpath link_root_classes \
-d bin
Will compile all sources for package com.bar and its subpackages, found below first_root,
except the package com.bar.foo (and its subpackages), for which the sources are picked
from second_root instead. It will link against classes in link_root_classes and against
sources in link_root_sources, but will not see (try to link against) sources matching org.net.*
but will link against org.net* classes (if they exist) in link_root_classes.
(If you want a set of complex filter rules to be applied to several source directories, without
having to repeat the the filter rules for each root. You can use the explicit -src option. For example:
sjavac -x "com.foo.*" -src root1:root2:root3 )
The resulting classes are written into bin.
*/
// This is the final destination for classes and copied files.
private File bin_dir;
// This is where the annotation process will put generated sources.
private File gensrc_dir;
// This is where javac -h puts the generated c-header files.
private File header_dir;
// This file contains the list of sources genereated by the makefile.
// We double check that our calculated list of sources matches this list,
// if not, then we terminate with an error!
private File makefile_source_list;
// The challenging task to manage an incremental build is done by javac_state.
private JavacState javac_state;
// The suffix rules tells you for example, that .java files should be compiled,
// and .html files should be copied and .properties files be translated.
Map<String,Transformer> suffix_rules;
public static void main(String... args) {
if (args.length > 0 && args[0].startsWith("--startserver:")) {
if (args.length>1) {
Log.error("When spawning a background server, only a single --startserver argument is allowed.");
return;
}
// Spawn a background server.
int rc = JavacServer.startServer(args[0], System.err);
System.exit(rc);
}
Main main = new Main();
int rc = main.go(args, System.out, System.err);
// Remove the portfile, but only if this background=false was used.
JavacServer.cleanup(args);
System.exit(rc);
}
private void printHelp() {
System.out.println("Usage: sjavac <options>\n"+
"where required options are:\n"+
"dir Compile all sources in dir recursively\n"+
"-d dir Store generated classes here and the javac_state file\n"+
"--server:portfile=/tmp/abc Use a background sjavac server\n\n"+
"All other arguments as javac, except -implicit:none which is forced by default.\n"+
"No java source files can be supplied on the command line, nor can an @file be supplied.\n\n"+
"Warning!\n"+
"This tool might disappear at any time, and its command line options might change at any time!");
}
public int go(String[] args, PrintStream out, PrintStream err) {
try {
if (args.length == 0 || findJavaSourceFiles(args) || findAtFile(args) || null==Util.findServerSettings(args)) {
printHelp();
return 0;
}
Log.setLogLevel(findLogLevel(args), out, err);
String server_settings = Util.findServerSettings(args);
args = verifyImplicitOption(args);
// Find the source root directories, and add the -src option before these, if not there already.
args = addSrcBeforeDirectories(args);
// Check that there is at least one -src supplied.
checkSrcOption(args);
// Check that there is one -d supplied.
bin_dir = findDirectoryOption(args,"-d","output", true, false, true);
gensrc_dir = findDirectoryOption(args,"-s","gensrc", false, false, true);
header_dir = findDirectoryOption(args,"-h","headers", false, false, true);
makefile_source_list = findFileOption(args,"--compare-found-sources","makefile source list", false);
// Load the prev build state database.
javac_state = JavacState.load(args, bin_dir, gensrc_dir, header_dir,
findBooleanOption(args, "--permit-unidentified-artifacts"), out, err);
// Setup the suffix rules from the command line.
suffix_rules = javac_state.getJavaSuffixRule();
findTranslateOptions(args, suffix_rules);
if (suffix_rules.keySet().size() > 1 && gensrc_dir == null) {
Log.error("You have translators but no gensrc dir (-s) specified!");
return -1;
}
findCopyOptions(args, suffix_rules);
// All found modules are put here.
Map<String,Module> modules = new HashMap<String,Module>();
// We start out in the legacy empty no-name module.
// As soon as we stumble on a module-info.java file we change to that module.
Module current_module = new Module("", "");
modules.put("", current_module);
// Find all sources, use the suffix rules to know which files are sources.
Map<String,Source> sources = new HashMap<String,Source>();
// Find the files, this will automatically populate the found modules
// with found packages where the sources are found!
findFiles(args, "-src", suffix_rules.keySet(), sources, modules, current_module, false);
if (sources.isEmpty()) {
Log.error("Found nothing to compile!");
return -1;
}
// Find all source files allowable for linking.
// We might find more modules here as well.
Map<String,Source> sources_to_link_to = new HashMap<String,Source>();
// Always reuse -src for linking as well! This means that we might
// get two -sourcepath on the commandline after the rewrite, which is
// fine. We can have as many as we like. You need to have separate -src/-sourcepath/-classpath
// if you need different filtering rules for different roots. If you have the same filtering
// rules for all sourcepath roots, you can concatenate them using :(;) as before.
rewriteOptions(args, "-src", "-sourcepath");
findFiles(args, "-sourcepath", Util.set(".java"), sources_to_link_to, modules, current_module, true);
// Find all class files allowable for linking.
// And pickup knowledge of all modules found here.
// This cannot currently filter classes inside jar files.
Map<String,Source> classes_to_link_to = new HashMap<String,Source>();
// findFiles(args, "-classpath", Util.set(".class"), classes_to_link_to, modules, current_module, true);
// Find all module sources allowable for linking.
Map<String,Source> modules_to_link_to = new HashMap<String,Source>();
// findFiles(args, "-modulepath", Util.set(".class"), modules_to_link_to, modules, current_module, true);
// Add the set of sources to the build database.
javac_state.now().collectPackagesSourcesAndArtifacts(modules);
javac_state.now().checkInternalState("checking sources", false, sources);
javac_state.now().checkInternalState("checking linked sources", true, sources_to_link_to);
javac_state.setVisibleSources(sources_to_link_to);
// If there is any change in the source files, taint packages
// and mark the database in need of saving.
javac_state.checkSourceStatus(false);
// Find all existing artifacts. Their timestamp will match the last modified timestamps stored
// in javac_state, simply because loading of the JavacState will clean out all artifacts
// that do not match the javac_state database.
javac_state.findAllArtifacts();
// Remove unidentified artifacts from the bin, gensrc and header dirs.
// (Unless we allow them to be there.)
// I.e. artifacts that are not known according to the build database (javac_state).
// For examples, files that have been manually copied into these dirs.
// Artifacts with bad timestamps (ie the on disk timestamp does not match the timestamp
// in javac_state) have already been removed when the javac_state was loaded.
if (!findBooleanOption(args, "--permit-unidentified-artifacts")) {
javac_state.removeUnidentifiedArtifacts();
}
// Go through all sources and taint all packages that miss artifacts.
javac_state.taintPackagesThatMissArtifacts();
// Now clean out all known artifacts belonging to tainted packages.
javac_state.deleteClassArtifactsInTaintedPackages();
// Copy files, for example property files, images files, xml files etc etc.
javac_state.performCopying(bin_dir, suffix_rules);
// Translate files, for example compile properties or compile idls.
javac_state.performTranslation(gensrc_dir, suffix_rules);
// Add any potentially generated java sources to the tobe compiled list.
// (Generated sources must always have a package.)
Map<String,Source> generated_sources = new HashMap<String,Source>();
Source.scanRoot(gensrc_dir, Util.set(".java"), null, null, null, null,
generated_sources, modules, current_module, false, true, false);
javac_state.now().collectPackagesSourcesAndArtifacts(modules);
// Recheck the the source files and their timestamps again.
javac_state.checkSourceStatus(true);
// Now do a safety check that the list of source files is identical
// to the list Make believes we are compiling. If we do not get this
// right, then incremental builds will fail with subtility.
// If any difference is detected, then we will fail hard here.
// This is an important safety net.
javac_state.compareWithMakefileList(makefile_source_list);
// Do the compilations, repeatedly until no tainted packages exist.
boolean again;
// Collect the name of all compiled packages.
Set<String> recently_compiled = new HashSet<String>();
boolean[] rc = new boolean[1];
do {
// Clean out artifacts in tainted packages.
javac_state.deleteClassArtifactsInTaintedPackages();
again = javac_state.performJavaCompilations(bin_dir, server_settings, args, recently_compiled, rc);
if (!rc[0]) break;
} while (again);
// Only update the state if the compile went well.
if (rc[0]) {
javac_state.save();
// Collect all the artifacts.
javac_state.now().collectArtifacts(modules);
// Remove artifacts that were generated during the last compile, but not this one.
javac_state.removeSuperfluousArtifacts(recently_compiled);
}
return rc[0] ? 0 : -1;
} catch (ProblemException e) {
Log.error(e.getMessage());
return -1;
} catch (Exception e) {
e.printStackTrace(err);
return -1;
}
}
/**
* Are java source files passed on the command line?
*/
private boolean findJavaSourceFiles(String[] args) {
String prev = "";
for (String s : args) {
if (s.endsWith(".java") && !prev.equals("-xf") && !prev.equals("-if")) {
return true;
}
prev = s;
}
return false;
}
/**
* Is an at file passed on the command line?
*/
private boolean findAtFile(String[] args) {
for (String s : args) {
if (s.startsWith("@")) {
return true;
}
}
return false;
}
/**
* Find the log level setting.
*/
private String findLogLevel(String[] args) {
for (String s : args) {
if (s.startsWith("--log=") && s.length()>6) {
return s.substring(6);
}
if (s.equals("-verbose")) {
return "info";
}
}
return "info";
}
/**
* Remove smart javac wrapper arguments, before feeding
* the args to the plain javac.
*/
static String[] removeWrapperArgs(String[] args) {
String[] out = new String[args.length];
// The first source path index is remembered
// here. So that all following can be concatenated to it.
int source_path = -1;
// The same for class path.
int class_path = -1;
// And module path.
int module_path = -1;
int j = 0;
for (int i = 0; i<args.length; ++i) {
if (args[i].equals("-src") ||
args[i].equals("-x") ||
args[i].equals("-i") ||
args[i].equals("-xf") ||
args[i].equals("-if") ||
args[i].equals("-copy") ||
args[i].equals("-tr") ||
args[i].equals("-j")) {
// Just skip it and skip following value
i++;
} else if (args[i].startsWith("--server:")) {
// Just skip it.
} else if (args[i].startsWith("--log=")) {
// Just skip it.
} else if (args[i].equals("--permit-unidentified-artifacts")) {
// Just skip it.
} else if (args[i].equals("--permit-sources-without-package")) {
// Just skip it.
} else if (args[i].equals("--compare-found-sources")) {
// Just skip it and skip verify file name
i++;
} else if (args[i].equals("-sourcepath")) {
if (source_path == -1) {
source_path = j;
out[j] = args[i];
out[j+1] = args[i+1];
j+=2;
i++;
} else {
// Skip this and its argument, but
// append argument to found sourcepath.
out[source_path+1] = out[source_path+1]+File.pathSeparatorChar+args[i+1];
i++;
}
} else if (args[i].equals("-classpath")) {
if (class_path == -1) {
class_path = j;
out[j] = args[i];
out[j+1] = args[i+1];
j+=2;
i++;
} else {
// Skip this and its argument, but
// append argument to found sourcepath.
out[class_path+1] = out[class_path+1]+File.pathSeparatorChar+args[i+1];
i++;
}
} else if (args[i].equals("-modulepath")) {
if (module_path == -1) {
module_path = j;
out[j] = args[i];
out[j+1] = args[i+1];
j+=2;
i++;
} else {
// Skip this and its argument, but
// append argument to found sourcepath.
out[module_path+1] = out[module_path+1]+File.pathSeparatorChar+args[i+1];
i++;
}
} else {
// Copy argument.
out[j] = args[i];
j++;
}
}
String[] ret = new String[j];
System.arraycopy(out, 0, ret, 0, j);
return ret;
}
/**
* Make sure directory exist, create it if not.
*/
private static boolean makeSureExists(File dir) {
// Make sure the dest directories exist.
if (!dir.exists()) {
if (!dir.mkdirs()) {
Log.error("Could not create the directory "+dir.getPath());
return false;
}
}
return true;
}
/**
* Verify that a package pattern is valid.
*/
private static void checkPattern(String s) throws ProblemException {
// Package names like foo.bar.gamma are allowed, and
// package names suffixed with .* like foo.bar.* are
// also allowed.
Pattern p = Pattern.compile("[a-zA-Z_]{1}[a-zA-Z0-9_]*(\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*)*(\\.\\*)?+");
Matcher m = p.matcher(s);
if (!m.matches()) {
throw new ProblemException("The string \""+s+"\" is not a proper package name pattern.");
}
}
/**
* Verify that a translate pattern is valid.
*/
private static void checkTranslatePattern(String s) throws ProblemException {
// .prop=com.sun.tools.javac.smart.CompileProperties
// .idl=com.sun.corba.CompileIdl
// .g3=antlr.CompileGrammar,debug=true
Pattern p = Pattern.compile(
"\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*=[a-z_]{1}[a-z0-9_]*(\\.[a-z_]{1}[a-z0-9_]*)*"+
"(\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*)(,.*)?");
Matcher m = p.matcher(s);
if (!m.matches()) {
throw new ProblemException("The string \""+s+"\" is not a proper translate pattern.");
}
}
/**
* Verify that a copy pattern is valid.
*/
private static void checkCopyPattern(String s) throws ProblemException {
// .gif
// .html
Pattern p = Pattern.compile(
"\\.[a-zA-Z_]{1}[a-zA-Z0-9_]*");
Matcher m = p.matcher(s);
if (!m.matches()) {
throw new ProblemException("The string \""+s+"\" is not a proper suffix.");
}
}
/**
* Verify that a source file name is valid.
*/
private static void checkFilePattern(String s) throws ProblemException {
// File names like foo/bar/gamma/Bar.java are allowed,
// as well as /bar/jndi.properties as well as,
// */bar/Foo.java
Pattern p = null;
if (File.separatorChar == '\\') {
p = Pattern.compile("\\*?(.+\\\\)*.+");
}
else if (File.separatorChar == '/') {
p = Pattern.compile("\\*?(.+/)*.+");
} else {
throw new ProblemException("This platform uses the unsupported "+File.separatorChar+
" as file separator character. Please add support for it!");
}
Matcher m = p.matcher(s);
if (!m.matches()) {
throw new ProblemException("The string \""+s+"\" is not a proper file name.");
}
}
/**
* Scan the arguments to find an option is used.
*/
private static boolean hasOption(String[] args, String option) {
for (String a : args) {
if (a.equals(option)) return true;
}
return false;
}
/**
* Check if -implicit is supplied, if so check that it is none.
* If -implicit is not supplied, supply -implicit:none
* Only implicit:none is allowed because otherwise the multicore compilations
* and dependency tracking will be tangled up.
*/
private static String[] verifyImplicitOption(String[] args)
throws ProblemException {
boolean foundImplicit = false;
for (String a : args) {
if (a.startsWith("-implicit:")) {
foundImplicit = true;
if (!a.equals("-implicit:none")) {
throw new ProblemException("The only allowed setting for sjavac is -implicit:none, it is also the default.");
}
}
}
if (foundImplicit) {
return args;
}
// -implicit:none not found lets add it.
String[] newargs = new String[args.length+1];
System.arraycopy(args,0, newargs, 0, args.length);
newargs[args.length] = "-implicit:none";
return newargs;
}
/**
* Rewrite a single option into something else.
*/
private static void rewriteOptions(String[] args, String option, String new_option) {
for (int i=0; i<args.length; ++i) {
if (args[i].equals(option)) {
args[i] = new_option;
}
}
}
/**
* Scan the arguments to find an option that specifies a directory.
* Create the directory if necessary.
*/
private static File findDirectoryOption(String[] args, String option, String name, boolean needed, boolean allow_dups, boolean create)
throws ProblemException, ProblemException {
File dir = null;
for (int i = 0; i<args.length; ++i) {
if (args[i].equals(option)) {
if (dir != null) {
throw new ProblemException("You have already specified the "+name+" dir!");
}
if (i+1 >= args.length) {
throw new ProblemException("You have to specify a directory following "+option+".");
}
if (args[i+1].indexOf(File.pathSeparatorChar) != -1) {
throw new ProblemException("You must only specify a single directory for "+option+".");
}
dir = new File(args[i+1]);
if (!dir.exists()) {
if (!create) {
throw new ProblemException("This directory does not exist: "+dir.getPath());
} else
if (!makeSureExists(dir)) {
throw new ProblemException("Cannot create directory "+dir.getPath());
}
}
if (!dir.isDirectory()) {
throw new ProblemException("\""+args[i+1]+"\" is not a directory.");
}
}
}
if (dir == null && needed) {
throw new ProblemException("You have to specify "+option);
}
try {
if (dir != null)
return dir.getCanonicalFile();
} catch (IOException e) {
throw new ProblemException(""+e);
}
return null;
}
/**
* Option is followed by path.
*/
private static boolean shouldBeFollowedByPath(String o) {
return o.equals("-s") ||
o.equals("-h") ||
o.equals("-d") ||
o.equals("-sourcepath") ||
o.equals("-classpath") ||
o.equals("-bootclasspath") ||
o.equals("-src");
}
/**
* Add -src before source root directories if not already there.
*/
private static String[] addSrcBeforeDirectories(String[] args) {
List<String> newargs = new ArrayList<String>();
for (int i = 0; i<args.length; ++i) {
File dir = new File(args[i]);
if (dir.exists() && dir.isDirectory()) {
if (i == 0 || !shouldBeFollowedByPath(args[i-1])) {
newargs.add("-src");
}
}
newargs.add(args[i]);
}
return newargs.toArray(new String[0]);
}
/**
* Check the -src options.
*/
private static void checkSrcOption(String[] args)
throws ProblemException {
Set<File> dirs = new HashSet<File>();
for (int i = 0; i<args.length; ++i) {
if (args[i].equals("-src")) {
if (i+1 >= args.length) {
throw new ProblemException("You have to specify a directory following -src.");
}
StringTokenizer st = new StringTokenizer(args[i+1], File.pathSeparator);
while (st.hasMoreElements()) {
File dir = new File(st.nextToken());
if (!dir.exists()) {
throw new ProblemException("This directory does not exist: "+dir.getPath());
}
if (!dir.isDirectory()) {
throw new ProblemException("\""+dir.getPath()+"\" is not a directory.");
}
if (dirs.contains(dir)) {
throw new ProblemException("The src directory \""+dir.getPath()+"\" is specified more than once!");
}
dirs.add(dir);
}
}
}
if (dirs.isEmpty()) {
throw new ProblemException("You have to specify -src.");
}
}
/**
* Scan the arguments to find an option that specifies a file.
*/
private static File findFileOption(String[] args, String option, String name, boolean needed)
throws ProblemException, ProblemException {
File file = null;
for (int i = 0; i<args.length; ++i) {
if (args[i].equals(option)) {
if (file != null) {
throw new ProblemException("You have already specified the "+name+" file!");
}
if (i+1 >= args.length) {
throw new ProblemException("You have to specify a file following "+option+".");
}
file = new File(args[i+1]);
if (file.isDirectory()) {
throw new ProblemException("\""+args[i+1]+"\" is not a file.");
}
if (!file.exists() && needed) {
throw new ProblemException("The file \""+args[i+1]+"\" does not exist.");
}
}
}
if (file == null && needed) {
throw new ProblemException("You have to specify "+option);
}
return file;
}
/**
* Look for a specific switch, return true if found.
*/
public static boolean findBooleanOption(String[] args, String option) {
for (int i = 0; i<args.length; ++i) {
if (args[i].equals(option)) return true;
}
return false;
}
/**
* Scan the arguments to find an option that specifies a number.
*/
public static int findNumberOption(String[] args, String option) {
int rc = 0;
for (int i = 0; i<args.length; ++i) {
if (args[i].equals(option)) {
if (args.length > i+1) {
rc = Integer.parseInt(args[i+1]);
}
}
}
return rc;
}
/**
* Scan the arguments to find the option (-tr) that setup translation rules to java source
* from different sources. For example: .properties are translated using CompileProperties
* The found translators are stored as suffix rules.
*/
private static void findTranslateOptions(String[] args, Map<String,Transformer> suffix_rules)
throws ProblemException, ProblemException {
for (int i = 0; i<args.length; ++i) {
if (args[i].equals("-tr")) {
if (i+1 >= args.length) {
throw new ProblemException("You have to specify a translate rule following -tr.");
}
String s = args[i+1];
checkTranslatePattern(s);
int ep = s.indexOf("=");
String suffix = s.substring(0,ep);
String classname = s.substring(ep+1);
if (suffix_rules.get(suffix) != null) {
throw new ProblemException("You have already specified a "+
"rule for the suffix "+suffix);
}
if (s.equals(".class")) {
throw new ProblemException("You cannot have a translator for .class files!");
}
if (s.equals(".java")) {
throw new ProblemException("You cannot have a translator for .java files!");
}
String extra = null;
int exp = classname.indexOf(",");
if (exp != -1) {
extra = classname.substring(exp+1);
classname = classname.substring(0,exp);
}
try {
Class<?> cl = Class.forName(classname);
Transformer t = (Transformer)cl.newInstance();
t.setExtra(extra);
suffix_rules.put(suffix, t);
}
catch (Exception e) {
throw new ProblemException("Cannot use "+classname+" as a translator!");
}
}
}
}
/**
* Scan the arguments to find the option (-copy) that setup copying rules into the bin dir.
* For example: -copy .html
* The found copiers are stored as suffix rules as well. No translation is done, just copying.
*/
private void findCopyOptions(String[] args, Map<String,Transformer> suffix_rules)
throws ProblemException, ProblemException {
for (int i = 0; i<args.length; ++i) {
if (args[i].equals("-copy")) {
if (i+1 >= args.length) {
throw new ProblemException("You have to specify a translate rule following -tr.");
}
String s = args[i+1];
checkCopyPattern(s);
if (suffix_rules.get(s) != null) {
throw new ProblemException("You have already specified a "+
"rule for the suffix "+s);
}
if (s.equals(".class")) {
throw new ProblemException("You cannot have a copy rule for .class files!");
}
if (s.equals(".java")) {
throw new ProblemException("You cannot have a copy rule for .java files!");
}
suffix_rules.put(s, javac_state.getCopier());
}
}
}
/**
* Rewrite a / separated path into \ separated, but only
* if we are running on a platform were File.separatorChar=='\', ie winapi.
*/
private String fixupSeparator(String p) {
if (File.separatorChar == '/') return p;
return p.replaceAll("/", "\\\\");
}
/**
* Scan the arguments for -i -x -xf -if followed by the option
* -src, -sourcepath, -modulepath or -classpath and produce a map of all the
* files to referenced for that particular option.
*
* Store the found sources and the found modules in the supplied maps.
*/
private boolean findFiles(String[] args, String option, Set<String> suffixes,
Map<String,Source> found_files, Map<String, Module> found_modules,
Module current_module, boolean inLinksrc)
throws ProblemException, ProblemException
{
// Track which source roots, source path roots and class path roots have been added.
Set<File> roots = new HashSet<File>();
// Track the current set of package includes,excludes as well as excluded source files,
// to be used in the next -src/-sourcepath/-classpath
List<String> includes = new LinkedList<String>();
List<String> excludes = new LinkedList<String>();
List<String> excludefiles = new LinkedList<String>();
List<String> includefiles = new LinkedList<String>();
// This include is used to find all modules in the source.
List<String> moduleinfo = new LinkedList<String>();
moduleinfo.add("module-info.java");
for (int i = 0; i<args.length; ++i) {
if (args[i].equals("-i")) {
if (i+1 >= args.length) {
throw new ProblemException("You have to specify a package pattern following -i");
}
String incl = args[i+1];
checkPattern(incl);
includes.add(incl);
}
if (args[i].equals("-x")) {
if (i+1 >= args.length) {
throw new ProblemException("You have to specify a package pattern following -x");
}
String excl = args[i+1];
checkPattern(excl);
excludes.add(excl);
}
if (args[i].equals("-xf")) {
if (i+1 >= args.length) {
throw new ProblemException("You have to specify a file following -xf");
}
String exclf = args[i+1];
checkFilePattern(exclf);
exclf = Util.normalizeDriveLetter(exclf);
excludefiles.add(fixupSeparator(exclf));
}
if (args[i].equals("-if")) {
if (i+1 >= args.length) {
throw new ProblemException("You have to specify a file following -xf");
}
String inclf = args[i+1];
checkFilePattern(inclf);
inclf = Util.normalizeDriveLetter(inclf);
includefiles.add(fixupSeparator(inclf));
}
if (args[i].equals(option)) {
if (i+1 >= args.length) {
throw new ProblemException("You have to specify a directory following "+option);
}
String[] root_dirs = args[i+1].split(File.pathSeparator);
for (String r : root_dirs) {
File root = new File(r);
if (!root.isDirectory()) {
throw new ProblemException("\""+r+"\" is not a directory.");
}
try {
root = root.getCanonicalFile();
} catch (IOException e) {
throw new ProblemException(""+e);
}
if (roots.contains(root)) {
throw new ProblemException("\""+r+"\" has already been used for "+option);
}
if (roots.equals(bin_dir)) {
throw new ProblemException("\""+r+"\" cannot be used both for "+option+" and -d");
}
if (roots.equals(gensrc_dir)) {
throw new ProblemException("\""+r+"\" cannot be used both for "+option+" and -s");
}
if (roots.equals(header_dir)) {
throw new ProblemException("\""+r+"\" cannot be used both for "+option+" and -h");
}
roots.add(root);
Source.scanRoot(root, suffixes, excludes, includes, excludefiles, includefiles,
found_files, found_modules, current_module,
findBooleanOption(args, "--permit-sources-without-package"),
false, inLinksrc);
}
}
if (args[i].equals("-src") ||
args[i].equals("-sourcepath") ||
args[i].equals("-modulepath") ||
args[i].equals("-classpath"))
{
// Reset the includes,excludes and excludefiles after they have been used.
includes = new LinkedList<String>();
excludes = new LinkedList<String>();
excludefiles = new LinkedList<String>();
includefiles = new LinkedList<String>();
}
}
return true;
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.io.File;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The module is the root of a set of packages/sources/artifacts.
* At the moment there is only one module in use, the empty/no-name/default module.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class Module implements Comparable<Module> {
private String name;
private String dirname;
private Map<String,Package> packages = new HashMap<String,Package>();
private Map<String,Source> sources = new HashMap<String,Source>();
private Map<String,File> artifacts = new HashMap<String,File>();
public Module(String n, String dn) {
name = n;
dirname = n;
}
public String name() { return name; }
public String dirname() { return dirname; }
public Map<String,Package> packages() { return packages; }
public Map<String,Source> sources() { return sources; }
public Map<String,File> artifacts() { return artifacts; }
@Override
public boolean equals(Object o) {
return (o instanceof Module) && name.equals(((Module)o).name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public int compareTo(Module o) {
return name.compareTo(o.name);
}
public void save(StringBuilder b) {
b.append("M ").append(name).append(":").append("\n");
Package.savePackages(packages, b);
}
public static Module load(String l) {
int cp = l.indexOf(':',2);
if (cp == -1) return null;
String name = l.substring(2,cp);
return new Module(name, "");
}
public static void saveModules(Map<String,Module> ms, StringBuilder b)
{
for (Module m : ms.values()) {
m.save(b);
}
}
public void addPackage(Package p) {
packages.put(p.name(), p);
}
public Package lookupPackage(String pkg) {
Package p = packages.get(pkg);
if (p == null) {
p = new Package(this, pkg);
packages.put(pkg, p);
}
return p;
}
public void addSource(String pkg, Source src) {
Package p = lookupPackage(pkg);
src.setPackage(p);
p.addSource(src);
sources.put(src.file().getPath(), src);
}
public Source lookupSource(String path) {
return sources.get(path);
}
public void addArtifacts(String pkg, Set<URI> as) {
Package p = lookupPackage(pkg);
for (URI u : as) {
p.addArtifact(new File(u));
}
}
public void setDependencies(String pkg, Set<String> deps) {
Package p = lookupPackage(pkg);
p.setDependencies(deps);
}
public void setPubapi(String pkg, List<String> ps) {
Package p = lookupPackage(pkg);
p.setPubapi(ps);
}
public boolean hasPubapiChanged(String pkg, List<String> ps) {
Package p = lookupPackage(pkg);
return p.hasPubapiChanged(ps);
}
}

View File

@ -0,0 +1,307 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The Package class maintains meta information about a package.
* For example its sources, dependents,its pubapi and its artifacts.
*
* It might look odd that we track dependents/pubapi/artifacts on
* a package level, but it makes sense since recompiling a full package
* takes as long as recompiling a single java file in that package,
* if you take into account the startup time of the jvm.
*
* Also the dependency information will be much smaller (good for the javac_state file size)
* and it simplifies tracking artifact generation, you do not always know from which
* source a class file was generated, but you always know which package it belongs to.
*
* It is also educational to see package dependencies triggering recompilation of
* other packages. Even though the recompilation was perhaps not necessary,
* the visible recompilation of the dependent packages indicates how much circular
* dependencies your code has.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class Package implements Comparable<Package> {
// The module this package belongs to. (There is a legacy module with an empty string name,
// used for all legacy sources.)
private Module mod;
// Name of this package, module:pkg
// ex1 jdk.base:java.lang
// ex2 :java.lang (when in legacy mode)
private String name;
// The directory path to the package. If the package belongs to a module,
// then that module's file system name is part of the path.
private String dirname;
// This package depends on these packages.
private Set<String> dependencies = new HashSet<String>();
// This package has the following dependents, that depend on this package.
private Set<String> dependents = new HashSet<String>();
// This is the public api of this package.
private List<String> pubapi = new ArrayList<String>();
// Map from source file name to Source info object.
private Map<String,Source> sources = new HashMap<String,Source>();
// This package generated these artifacts.
private Map<String,File> artifacts = new HashMap<String,File>();
public Package(Module m, String n) {
int c = n.indexOf(":");
assert(c != -1);
String mn = n.substring(0,c);
assert(m.name().equals(m.name()));
name = n;
dirname = n.replace('.', File.separatorChar);
if (m.name().length() > 0) {
// There is a module here, prefix the module dir name to the path.
dirname = m.dirname()+File.separatorChar+dirname;
}
}
public Module mod() { return mod; }
public String name() { return name; }
public String dirname() { return dirname; }
public Map<String,Source> sources() { return sources; }
public Map<String,File> artifacts() { return artifacts; }
public List<String> pubapi() { return pubapi; }
public Set<String> dependencies() { return dependencies; }
public Set<String> dependents() { return dependents; }
@Override
public boolean equals(Object o) {
return (o instanceof Package) && name.equals(((Package)o).name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public int compareTo(Package o) {
return name.compareTo(o.name);
}
public void addSource(Source s) {
sources.put(s.file().getPath(), s);
}
public void addDependency(String d) {
dependencies.add(d);
}
public void addDependent(String d) {
dependents.add(d);
}
public void addPubapi(String p) {
pubapi.add(p);
}
/**
* Check if we have knowledge in the javac state that
* describe the results of compiling this package before.
*/
public boolean existsInJavacState() {
return artifacts.size() > 0 || pubapi.size() > 0;
}
public static List<String> pubapiToList(String ps)
{
String[] lines = ps.split("\n");
List<String> r = new ArrayList<String>();
for (String l : lines) {
r.add(l);
}
return r;
}
public boolean hasPubapiChanged(List<String> ps) {
Iterator<String> i = ps.iterator();
Iterator<String> j = pubapi.iterator();
int line = 0;
while (i.hasNext() && j.hasNext()) {
String is = i.next();
String js = j.next();
if (!is.equals(js)) {
Log.debug("Change in pubapi for package "+name+" line "+line);
Log.debug("Old: "+js);
Log.debug("New: "+is);
return true;
}
line++;
}
if ((i.hasNext() && !j.hasNext() ) ||
(!i.hasNext() && j.hasNext())) {
Log.debug("Change in pubapi for package "+name);
if (i.hasNext()) {
Log.debug("New has more lines!");
} else {
Log.debug("Old has more lines!");
}
return true;
}
return false;
}
public void setPubapi(List<String> ps) {
pubapi = ps;
}
public void setDependencies(Set<String> ds) {
dependencies = ds;
}
public void save(StringBuilder b) {
b.append("P ").append(name).append("\n");
Source.saveSources(sources, b);
saveDependencies(b);
savePubapi(b);
saveArtifacts(b);
}
static public Package load(Module module, String l) {
String name = l.substring(2);
return new Package(module, name);
}
public void loadDependency(String l) {
String n = l.substring(2);
addDependency(n);
}
public void loadPubapi(String l) {
String pi = l.substring(2);
addPubapi(pi);
}
public void saveDependencies(StringBuilder b) {
List<String> sorted_dependencies = new ArrayList<String>();
for (String key : dependencies) {
sorted_dependencies.add(key);
}
Collections.sort(sorted_dependencies);
for (String a : sorted_dependencies) {
b.append("D "+a+"\n");
}
}
public void savePubapi(StringBuilder b) {
for (String l : pubapi) {
b.append("I "+l+"\n");
}
}
public static void savePackages(Map<String,Package> packages, StringBuilder b) {
List<String> sorted_packages = new ArrayList<String>();
for (String key : packages.keySet() ) {
sorted_packages.add(key);
}
Collections.sort(sorted_packages);
for (String s : sorted_packages) {
Package p = packages.get(s);
p.save(b);
}
}
public void addArtifact(String a) {
artifacts.put(a, new File(a));
}
public void addArtifact(File f) {
artifacts.put(f.getPath(), f);
}
public void addArtifacts(Set<URI> as) {
for (URI u : as) {
addArtifact(new File(u));
}
}
public void setArtifacts(Set<URI> as) {
assert(!artifacts.isEmpty());
artifacts = new HashMap<String,File>();
addArtifacts(as);
}
public void loadArtifact(String l) {
// Find next space after "A ".
int dp = l.indexOf(' ',2);
String fn = l.substring(2,dp);
long last_modified = Long.parseLong(l.substring(dp+1));
File f = new File(fn);
if (f.exists() && f.lastModified() != last_modified) {
// Hmm, the artifact on disk does not have the same last modified
// timestamp as the information from the build database.
// We no longer trust the artifact on disk. Delete it.
// The smart javac wrapper will then rebuild the artifact.
Log.debug("Removing "+f.getPath()+" since its timestamp does not match javac_state.");
f.delete();
}
artifacts.put(f.getPath(), f);
}
public void saveArtifacts(StringBuilder b) {
List<File> sorted_artifacts = new ArrayList<File>();
for (File f : artifacts.values()) {
sorted_artifacts.add(f);
}
Collections.sort(sorted_artifacts);
for (File f : sorted_artifacts) {
// The last modified information is only used
// to detect tampering with the output dir.
// If the outputdir has been modified, not by javac,
// then a mismatch will be detected in the last modified
// timestamps stored in the build database compared
// to the timestamps on disk and the artifact will be deleted.
b.append("A "+f.getPath()+" "+f.lastModified()+"\n");
}
}
/**
* Always clean out a tainted package before it is recompiled.
*/
public void deleteArtifacts() {
for (File a : artifacts.values()) {
a.delete();
}
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
/**
* Used to signal serious problems when running sjavac.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class ProblemException extends Exception {
static final long serialVersionUID = -3387516993124229949L;
public ProblemException(String s) {
super(s);
}
}

View File

@ -0,0 +1,400 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.io.File;
import java.util.Set;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
/** A Source object maintains information about a source file.
* For example which package it belongs to and kind of source it is.
* The class also knows how to find source files (scanRoot) given include/exclude
* patterns and a root.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class Source implements Comparable<Source> {
// The package the source belongs to.
private Package pkg;
// Name of this source file, relative its source root.
// For example: java/lang/Object.java
// Or if the source file is inside a module:
// jdk.base/java/lang/Object.java
private String name;
// What kind of file is this.
private String suffix;
// When this source file was last_modified
private long lastModified;
// The source File.
private File file;
// The source root under which file resides.
private File root;
// If the source is generated.
private boolean isGenerated;
// If the source is only linked to, not compiled.
private boolean linkedOnly;
@Override
public boolean equals(Object o) {
return (o instanceof Source) && name.equals(((Source)o).name);
}
@Override
public int compareTo(Source o) {
return name.compareTo(o.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
public Source(Module m, String n, File f, File r) {
name = n;
int dp = n.lastIndexOf(".");
if (dp != -1) {
suffix = n.substring(dp);
} else {
suffix = "";
}
file = f;
root = r;
lastModified = f.lastModified();
linkedOnly = false;
}
public Source(Package p, String n, long lm) {
pkg = p;
name = n;
int dp = n.lastIndexOf(".");
if (dp != -1) {
suffix = n.substring(dp);
} else {
suffix = "";
}
file = null;
root = null;
lastModified = lm;
linkedOnly = false;
int ls = n.lastIndexOf('/');
}
public String name() { return name; }
public String suffix() { return suffix; }
public Package pkg() { return pkg; }
public File file() { return file; }
public File root() { return root; }
public long lastModified() {
return lastModified;
}
public void setPackage(Package p) {
pkg = p;
}
public void markAsGenerated() {
isGenerated = true;
}
public boolean isGenerated() {
return isGenerated;
}
public void markAsLinkedOnly() {
linkedOnly = true;
}
public boolean isLinkedOnly() {
return linkedOnly;
}
private void save(StringBuilder b) {
String CL = linkedOnly?"L":"C";
String GS = isGenerated?"G":"S";
b.append(GS+" "+CL+" "+name+" "+file.lastModified()+"\n");
}
// Parse a line that looks like this:
// S C /code/alfa/A.java 1357631228000
static public Source load(Package lastPackage, String l, boolean isGenerated) {
int sp = l.indexOf(' ',4);
if (sp == -1) return null;
String name = l.substring(4,sp);
long last_modified = Long.parseLong(l.substring(sp+1));
boolean isLinkedOnly = false;
if (l.charAt(2) == 'L') {
isLinkedOnly = true;
} else if (l.charAt(2) == 'C') {
isLinkedOnly = false;
} else return null;
Source s = new Source(lastPackage, name, last_modified);
s.file = new File(name);
if (isGenerated) s.markAsGenerated();
if (isLinkedOnly) s.markAsLinkedOnly();
return s;
}
public static void saveSources(Map<String,Source> sources, StringBuilder b) {
List<String> sorted_sources = new ArrayList<String>();
for (String key : sources.keySet()) {
sorted_sources.add(key);
}
Collections.sort(sorted_sources);
for (String key : sorted_sources) {
Source s = sources.get(key);
s.save(b);
}
}
/**
* Recurse into the directory root and find all files matchine the excl/incl/exclfiles/inclfiles rules.
* Detects the existence of module-info.java files and presumes that the directory it resides in
* is the name of the current module.
*/
static public void scanRoot(File root,
Set<String> suffixes,
List<String> excludes, List<String> includes,
List<String> excludeFiles, List<String> includeFiles,
Map<String,Source> foundFiles,
Map<String,Module> foundModules,
Module currentModule,
boolean permitSourcesWithoutPackage,
boolean inGensrc,
boolean inLinksrc)
throws ProblemException {
if (root == null) return;
int root_prefix = root.getPath().length()+1;
// This is the root source directory, it must not contain any Java sources files
// because we do not allow Java source files without a package.
// (Unless of course --permit-sources-without-package has been specified.)
// It might contain other source files however, (for -tr and -copy) these will
// always be included, since no package pattern can match the root directory.
currentModule = addFilesInDir(root, root_prefix, root, suffixes, permitSourcesWithoutPackage,
excludeFiles, includeFiles, false,
foundFiles, foundModules, currentModule,
inGensrc, inLinksrc);
File[] dirfiles = root.listFiles();
for (File d : dirfiles) {
if (d.isDirectory()) {
// Descend into the directory structure.
scanDirectory(d, root_prefix, root, suffixes,
excludes, includes, excludeFiles, includeFiles,
false, foundFiles, foundModules, currentModule, inGensrc, inLinksrc);
}
}
}
/**
* Test if a path matches any of the patterns given.
* The pattern foo.bar matches only foo.bar
* The pattern foo.* matches foo.bar and foo.bar.zoo etc
*/
static private boolean hasMatch(String path, List<String> patterns) {
for (String p : patterns) {
// Exact match
if (p.equals(path)) {
return true;
}
// Single dot the end matches this package and all its subpackages.
if (p.endsWith(".*")) {
// Remove the wildcard
String patprefix = p.substring(0,p.length()-2);
// Does the path start with the pattern prefix?
if (path.startsWith(patprefix)) {
// If the path has the same length as the pattern prefix, then it is a match.
// If the path is longer, then make sure that
// the next part of the path starts with a dot (.) to prevent
// wildcard matching in the middle of a package name.
if (path.length()==patprefix.length() || path.charAt(patprefix.length())=='.') {
return true;
}
}
}
}
return false;
}
/**
* Matches patterns with the asterisk first. */
// The pattern foo/bar.java only matches foo/bar.java
// The pattern */bar.java matches foo/bar.java and zoo/bar.java etc
static private boolean hasFileMatch(String path, List<String> patterns) {
path = Util.normalizeDriveLetter(path);
for (String p : patterns) {
// Exact match
if (p.equals(path)) {
return true;
}
// Single dot the end matches this package and all its subpackages.
if (p.startsWith("*")) {
// Remove the wildcard
String patsuffix = p.substring(1);
// Does the path start with the pattern prefix?
if (path.endsWith(patsuffix)) {
return true;
}
}
}
return false;
}
/**
* Add the files in the directory, assuming that the file has not been excluded.
* Returns a fresh Module object, if this was a dir with a module-info.java file.
*/
static private Module addFilesInDir(File dir, int rootPrefix, File root,
Set<String> suffixes, boolean allow_javas,
List<String> excludeFiles, List<String> includeFiles, boolean all,
Map<String,Source> foundFiles,
Map<String,Module> foundModules,
Module currentModule,
boolean inGensrc,
boolean inLinksrc)
throws ProblemException
{
for (File f : dir.listFiles()) {
if (f.isFile()) {
boolean should_add =
(excludeFiles == null || excludeFiles.isEmpty() || !hasFileMatch(f.getPath(), excludeFiles))
&& (includeFiles == null || includeFiles.isEmpty() || hasFileMatch(f.getPath(), includeFiles));
if (should_add) {
if (!allow_javas && f.getName().endsWith(".java")) {
throw new ProblemException("No .java files are allowed in the source root "+dir.getPath()+
", please remove "+f.getName());
}
// Extract the file name relative the root.
String fn = f.getPath().substring(rootPrefix);
// Extract the package name.
int sp = fn.lastIndexOf(File.separatorChar);
String pkg = "";
if (sp != -1) {
pkg = fn.substring(0,sp).replace(File.separatorChar,'.');
}
// Is this a module-info.java file?
if (fn.endsWith("module-info.java")) {
// Aha! We have recursed into a module!
if (!currentModule.name().equals("")) {
throw new ProblemException("You have an extra module-info.java inside a module! Please remove "+fn);
}
String module_name = fn.substring(0,fn.length()-16);
currentModule = new Module(module_name, f.getPath());
foundModules.put(module_name, currentModule);
}
// Extract the suffix.
int dp = fn.lastIndexOf(".");
String suffix = "";
if (dp > 0) {
suffix = fn.substring(dp);
}
// Should the file be added?
if (all || suffixes.contains(suffix)) {
Source of = foundFiles.get(f.getPath());
if (of != null) {
throw new ProblemException("You have already added the file "+fn+" from "+of.file().getPath());
}
of = currentModule.lookupSource(f.getPath());
if (of != null) {
// Oups, the source is already added, could be ok, could be not, lets check.
if (inLinksrc) {
// So we are collecting sources for linking only.
if (of.isLinkedOnly()) {
// Ouch, this one is also for linking only. Bad.
throw new ProblemException("You have already added the link only file "+fn+" from "+of.file().getPath());
}
// Ok, the existing source is to be compiled. Thus this link only is redundant
// since all compiled are also linked to. Continue to the next source.
// But we need to add the source, so that it will be visible to linking,
// if not the multi core compile will fail because a JavaCompiler cannot
// find the necessary dependencies for its part of the source.
foundFiles.put(f.getPath(), of);
continue;
} else {
// We are looking for sources to compile, if we find an existing to be compiled
// source with the same name, it is an internal error, since we must
// find the sources to be compiled before we find the sources to be linked to.
throw new ProblemException("Internal error: Double add of file "+fn+" from "+of.file().getPath());
}
}
Source s = new Source(currentModule, f.getPath(), f, root);
if (inGensrc) s.markAsGenerated();
if (inLinksrc) {
s.markAsLinkedOnly();
}
pkg = currentModule.name()+":"+pkg;
foundFiles.put(f.getPath(), s);
currentModule.addSource(pkg, s);
}
}
}
}
return currentModule;
}
private static boolean gurka = false;
static private void scanDirectory(File dir, int rootPrefix, File root,
Set<String> suffixes,
List<String> excludes, List<String> includes,
List<String> excludeFiles, List<String> includeFiles, boolean all,
Map<String,Source> foundFiles,
Map<String,Module> foundModules,
Module currentModule, boolean inGensrc, boolean inLinksrc)
throws ProblemException {
String pkg_name = "";
// Remove the root prefix from the dir path, and replace file separator with dots
// to get the package name.
if (dir.getPath().length() > rootPrefix) {
pkg_name = dir.getPath().substring(rootPrefix).replace(File.separatorChar,'.');
}
// Should this package directory be included and not excluded?
if (all || ((includes==null || includes.isEmpty() || hasMatch(pkg_name, includes)) &&
(excludes==null || excludes.isEmpty() || !hasMatch(pkg_name, excludes)))) {
// Add the source files.
currentModule = addFilesInDir(dir, rootPrefix, root, suffixes, true, excludeFiles, includeFiles, all,
foundFiles, foundModules, currentModule, inGensrc, inLinksrc);
}
for (File d : dir.listFiles()) {
if (d.isDirectory()) {
// Descend into the directory structure.
scanDirectory(d, rootPrefix, root, suffixes,
excludes, includes, excludeFiles, includeFiles, all,
foundFiles, foundModules, currentModule, inGensrc, inLinksrc);
}
}
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.io.PrintStream;
import java.net.URI;
import java.util.Set;
import java.util.Map;
/**
* The transform interface is used to transform content inside a package, from one form to another.
* Usually the output form is an unpredictable number of output files. (eg class files)
* but can also be an unpredictable number of generated source files (eg idl2java)
* or a single predictable output file (eg when copying,cleaning or compiling a properties file).
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public interface Transformer
{
/**
* The transform method takes a set of package names, mapped to their source files and to the
* pubapis of the packages.
*
* The transform implementation must:
* store the names of the generated artifacts for each package into package_artifacts
* store found dependencies to other packages into the supplied set package_dependencies
* store the public api for a package into the supplied set package_pubapis
*
* Any benign messages as a result of running the transform
* are written into stdout, and errors are written to stderr.
*
* The debug_level can be 0=silent (only warnings and errors) 1=normal 2=verbose 3 or greater=debug
* setExtra is used to set the extra information information that can be passed on
* the command line to the smart javac wrapper.
*
* If sjavac is building incrementally from an existing javac_state, the var incremental is true.
*
* The transformer will only be called if some source in the package (or dependency) has
* a modified timestamp. Thus the transformer might get called with many sources, of which
* only one has changed. The transformer is allowed to regenerate all artifacts but
* a better transformer will only write those artifacts that need updating.
*
* However the transformer must verify that the existing artifacts really are there!
* and it must always update package_artifacts, package_dependencies, and package_pubapis correctly.
* This means that at least for Java source, it will always have to recompile the sources.
*
* The transformer is allowed to put files anywhere in the dest_root.
* An example of this is, can be the META-INF transformer that copy files
* below META-INF directories to the single META-INF directory below dest_root.
*
* False is returned if there was an error that prevented the transform.
* I.e. something was printed on stderr.
*
* If num_cores is set to a non-zero value. The transform should attempt to use no more than these
* number of threads for heavy work.
*/
boolean transform(Map<String,Set<URI>> pkgSrcs,
Set<URI> visibleSources,
Map<URI,Set<String>> visibleClasses,
Map<String,Set<String>> oldPackageDependencies,
URI destRoot,
Map<String,Set<URI>> packageArtifacts,
Map<String,Set<String>> packageDependencies,
Map<String,String> packagePublicApis,
int debugLevel,
boolean incremental,
int numCores,
PrintStream out,
PrintStream err);
void setExtra(String e);
void setExtra(String[] args);
}

View File

@ -0,0 +1,160 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac;
import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
/**
* Utilities.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class Util {
public static String toFileSystemPath(String pkgId) {
if (pkgId == null || pkgId.length()==0) return null;
String pn;
if (pkgId.charAt(0) == ':') {
// When the module is the default empty module.
// Do not prepend the module directory, because there is none.
// Thus :java.foo.bar translates to java/foo/bar (or \)
pn = pkgId.substring(1).replace('.',File.separatorChar);
} else {
// There is a module. Thus jdk.base:java.foo.bar translates
// into jdk.base/java/foo/bar
int cp = pkgId.indexOf(':');
String mn = pkgId.substring(0,cp);
pn = mn+File.separatorChar+pkgId.substring(cp+1).replace('.',File.separatorChar);
}
return pn;
}
public static String justPackageName(String pkgName) {
int c = pkgName.indexOf(":");
assert(c != -1);
return pkgName.substring(c+1);
}
public static String extractStringOption(String opName, String s) {
int p = s.indexOf(opName+"=");
if (p == -1) return null;
p+=opName.length()+1;
int pe = s.indexOf(',', p);
if (pe == -1) pe = s.length();
return s.substring(p, pe);
}
public static int extractIntOption(String opName, String s) {
int p = s.indexOf(opName+"=");
if (p == -1) return 0;
p+=opName.length()+1;
int pe = s.indexOf(',', p);
if (pe == -1) pe = s.length();
int v = 0;
try {
v = Integer.parseInt(s.substring(p, pe));
} catch (Exception e) {}
return v;
}
/**
* Clean out unwanted sub options supplied inside a primary option.
* For example to only had portfile remaining from:
* settings="--server:id=foo,portfile=bar"
* do settings = cleanOptions("--server:",Util.set("-portfile"),settings);
* now settings equals "--server:portfile=bar"
*
* @param optionPrefix The option name, including colon, eg --server:
* @param allowsSubOptions A set of the allowed sub options, id portfile etc.
* @param s The option settings string.
*/
public static String cleanSubOptions(String optionPrefix, Set<String> allowedSubOptions, String s) {
StringBuilder sb = new StringBuilder();
if (!s.startsWith(optionPrefix)) return "";
StringTokenizer st = new StringTokenizer(s.substring(optionPrefix.length()), ",");
while (st.hasMoreTokens()) {
String o = st.nextToken();
int p = o.indexOf('=');
if (p>0) {
String key = o.substring(0,p);
String val = o.substring(p+1);
if (allowedSubOptions.contains(key)) {
if (sb.length() > 0) sb.append(',');
sb.append(key+"="+val);
}
}
}
return sb.toString();
}
/**
* Convenience method to create a set with strings.
*/
public static Set<String> set(String... ss) {
Set<String> set = new HashSet<String>();
set.addAll(Arrays.asList(ss));
return set;
}
/**
* Normalize windows drive letter paths to upper case to enable string
* comparison.
*
* @param file File name to normalize
* @return The normalized string if file has a drive letter at the beginning,
* otherwise the original string.
*/
public static String normalizeDriveLetter(String file) {
if (file.length() > 2 && file.charAt(1) == ':') {
return Character.toUpperCase(file.charAt(0)) + file.substring(1);
} else if (file.length() > 3 && file.charAt(0) == '*'
&& file.charAt(2) == ':') {
// Handle a wildcard * at the beginning of the string.
return file.substring(0, 1) + Character.toUpperCase(file.charAt(1))
+ file.substring(2);
}
return file;
}
/**
* Locate the setting for the server properties.
*/
public static String findServerSettings(String[] args) {
for (String s : args) {
if (s.startsWith("--server:")) {
return s;
}
}
return null;
}
}

View File

@ -0,0 +1,188 @@
/*
* Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac.comp;
import javax.lang.model.element.Element;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Name;
/** Utility class containing dependency information between packages
* and the pubapi for a package.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class Dependencies {
protected static final Context.Key<Dependencies> dependenciesKey =
new Context.Key<Dependencies>();
// The log to be used for error reporting.
protected Log log;
// Map from package name to packages that the package depends upon.
protected Map<Name,Set<Name>> deps;
// This is the set of all packages that are supplied
// through the java files at the command line.
protected Set<Name> explicitPackages;
// Map from a package name to its public api.
// Will the Name encode the module in the future?
// If not, this will have to change to map from Module+Name to public api.
protected Map<Name,StringBuffer> publicApiPerClass;
public static Dependencies instance(Context context) {
Dependencies instance = context.get(dependenciesKey);
if (instance == null)
instance = new Dependencies(context);
return instance;
}
private Dependencies(Context context) {
context.put(dependenciesKey, this);
log = Log.instance(context);
}
public void reset()
{
deps = new HashMap<Name, Set<Name>>();
explicitPackages = new HashSet<Name>();
publicApiPerClass = new HashMap<Name,StringBuffer>();
}
/**
* Fetch the set of dependencies that are relevant to the compile
* that has just been performed. I.e. we are only interested in
* dependencies for classes that were explicitly compiled.
* @return
*/
public Map<String,Set<String>> getDependencies() {
Map<String,Set<String>> new_deps = new HashMap<String,Set<String>>();
if (explicitPackages == null) return new_deps;
for (Name pkg : explicitPackages) {
Set<Name> set = deps.get(pkg);
if (set != null) {
Set<String> new_set = new_deps.get(pkg.toString());
if (new_set == null) {
new_set = new HashSet<String>();
// Modules beware....
new_deps.put(":"+pkg.toString(), new_set);
}
for (Name d : set) {
new_set.add(":"+d.toString());
}
}
}
return new_deps;
}
class CompareNames implements Comparator<Name> {
public int compare(Name a, Name b) {
return a.toString().compareTo(b.toString());
}
public boolean equals(Object obj) {
return super.equals(obj);
}
}
/**
* Convert the map from class names to their pubapi to a map
* from package names to their pubapi (which is the sorted concatenation
* of all the class pubapis)
*/
public Map<String,String> getPubapis() {
Map<String,String> publicApiPerPackage = new HashMap<String,String>();
if (publicApiPerClass == null) return publicApiPerPackage;
Name[] keys = publicApiPerClass.keySet().toArray(new Name[0]);
Arrays.sort(keys, new CompareNames());
StringBuffer newPublicApi = new StringBuffer();
int i=0;
String prevPkg = "";
for (Name k : keys) {
String cn = k.toString();
String pn = "";
int dp = cn.lastIndexOf('.');
if (dp != -1) {
pn = cn.substring(0,dp);
}
if (!pn.equals(prevPkg)) {
if (!prevPkg.equals("")) {
// Add default module name ":"
publicApiPerPackage.put(":"+prevPkg, newPublicApi.toString());
}
newPublicApi = new StringBuffer();
prevPkg = pn;
}
newPublicApi.append(publicApiPerClass.get(k));
i++;
}
if (!prevPkg.equals(""))
publicApiPerPackage.put(":"+prevPkg, newPublicApi.toString());
return publicApiPerPackage;
}
/**
* Visit the api of a class and construct a pubapi string and
* store it into the pubapi_perclass map.
*/
public void visitPubapi(Element e) {
Name n = ((ClassSymbol)e).fullname;
Name p = ((ClassSymbol)e).packge().fullname;
StringBuffer sb = publicApiPerClass.get(n);
assert(sb == null);
sb = new StringBuffer();
PubapiVisitor v = new PubapiVisitor(sb);
v.visit(e);
if (sb.length()>0) {
publicApiPerClass.put(n, sb);
}
explicitPackages.add(p);
}
/**
* Collect a dependency. curr_pkg is marked as depending on dep_pkg.
*/
public void collect(Name currPkg, Name depPkg) {
if (!currPkg.equals(depPkg)) {
Set<Name> theset = deps.get(currPkg);
if (theset==null) {
theset = new HashSet<Name>();
deps.put(currPkg, theset);
}
theset.add(depPkg);
}
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac.comp;
import java.util.StringTokenizer;
import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.sjavac.server.CompilerThread;
import java.io.File;
/** Subclass to Resolve that overrides collect.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class JavaCompilerWithDeps extends JavaCompiler {
/** The dependency database
*/
protected Dependencies deps;
protected CompilerThread compilerThread;
public JavaCompilerWithDeps(Context context, CompilerThread t) {
super(context);
deps = Dependencies.instance(context);
compilerThread = t;
needRootClasses = true;
}
public static void preRegister(Context context, final CompilerThread t) {
context.put(compilerKey, new Context.Factory<JavaCompiler>() {
public JavaCompiler make(Context c) {
JavaCompiler instance = new JavaCompilerWithDeps(c, t);
c.put(JavaCompiler.class, instance);
return instance;
}
});
}
/** Collect the public apis of classes supplied explicitly for compilation.
* @param sym The class to visit.
*/
@Override
public void reportPublicApi(ClassSymbol sym) {
// The next test will catch when source files are located in the wrong directory!
// This ought to be moved into javac as a new warning, or perhaps as part
// of the auxiliary class warning.
// For example if sun.swing.BeanInfoUtils
// is in fact stored in: /mybuild/jdk/gensrc/javax/swing/beaninfo/BeanInfoUtils.java
// We do not need to test that BeanInfoUtils is stored in a file named BeanInfoUtils
// since this is checked earlier.
if (sym.sourcefile != null) {
// Rewrite sun.swing.BeanInfoUtils into /sun/swing/
StringBuilder pathb = new StringBuilder();
StringTokenizer qn = new StringTokenizer(sym.packge().toString(), ".");
boolean first = true;
while (qn.hasMoreTokens()) {
String o = qn.nextToken();
pathb.append("/");
pathb.append(o);
first = false;
}
pathb.append("/");
String path = pathb.toString();
// Now cut the uri to be: file:///mybuild/jdk/gensrc/javax/swing/beaninfo/
String p = sym.sourcefile.toUri().getPath();
// Do not use File.separatorChar here, a URI always uses slashes /.
int i = p.lastIndexOf("/");
String pp = p.substring(0,i+1);
// Now check if the truncated uri ends with the path. (It does not == failure!)
if (path.length() > 0 && !path.equals("/unnamed package/") && !pp.endsWith(path)) {
compilerThread.logError("Error: The source file "+sym.sourcefile.getName()+
" is located in the wrong package directory, because it contains the class "+
sym.getQualifiedName());
}
}
deps.visitPubapi(sym);
}
}

View File

@ -0,0 +1,157 @@
/*
* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac.comp;
import java.util.Iterator;
import java.util.List;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementScanner6;
/** Utility class that constructs a textual representation
* of the public api of a class.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class PubapiVisitor extends ElementScanner6<Void, Void> {
StringBuffer sb;
// Important that it is 1! Part of protocol over wire, silly yes.
// Fix please.
int indent = 1;
public PubapiVisitor(StringBuffer sb) {
this.sb = sb;
}
String depth(int l) {
return " ".substring(0, l);
}
@Override
public Void visitType(TypeElement e, Void p) {
if (e.getModifiers().contains(Modifier.PUBLIC)
|| e.getModifiers().contains(Modifier.PROTECTED))
{
sb.append(depth(indent) + "TYPE " + e.getQualifiedName() + "\n");
indent += 2;
Void v = super.visitType(e, p);
indent -= 2;
return v;
}
return null;
}
@Override
public Void visitVariable(VariableElement e, Void p) {
if (e.getModifiers().contains(Modifier.PUBLIC)
|| e.getModifiers().contains(Modifier.PROTECTED)) {
sb.append(depth(indent)).append("VAR ")
.append(makeVariableString(e)).append("\n");
}
// Safe to not recurse here, because the only thing
// to visit here is the constructor of a variable declaration.
// If it happens to contain an anonymous inner class (which it might)
// then this class is never visible outside of the package anyway, so
// we are allowed to ignore it here.
return null;
}
@Override
public Void visitExecutable(ExecutableElement e, Void p) {
if (e.getModifiers().contains(Modifier.PUBLIC)
|| e.getModifiers().contains(Modifier.PROTECTED)) {
sb.append(depth(indent)).append("METHOD ")
.append(makeMethodString(e)).append("\n");
}
return null;
}
/**
* Creates a String representation of a method element with everything
* necessary to track all public aspects of it in an API.
* @param e Element to create String for.
* @return String representation of element.
*/
protected String makeMethodString(ExecutableElement e) {
StringBuilder result = new StringBuilder();
for (Modifier modifier : e.getModifiers()) {
result.append(modifier.toString());
result.append(" ");
}
result.append(e.getReturnType().toString());
result.append(" ");
result.append(e.toString());
List<? extends TypeMirror> thrownTypes = e.getThrownTypes();
if (!thrownTypes.isEmpty()) {
result.append(" throws ");
for (Iterator<? extends TypeMirror> iterator = thrownTypes
.iterator(); iterator.hasNext();) {
TypeMirror typeMirror = iterator.next();
result.append(typeMirror.toString());
if (iterator.hasNext()) {
result.append(", ");
}
}
}
return result.toString();
}
/**
* Creates a String representation of a variable element with everything
* necessary to track all public aspects of it in an API.
* @param e Element to create String for.
* @return String representation of element.
*/
protected String makeVariableString(VariableElement e) {
StringBuilder result = new StringBuilder();
for (Modifier modifier : e.getModifiers()) {
result.append(modifier.toString());
result.append(" ");
}
result.append(e.asType().toString());
result.append(" ");
result.append(e.toString());
Object value = e.getConstantValue();
if (value != null) {
result.append(" = ");
if (e.asType().toString().equals("char")) {
int v = (int)value.toString().charAt(0);
result.append("'\\u"+Integer.toString(v,16)+"'");
} else {
result.append(value.toString());
}
}
return result.toString();
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac.comp;
import com.sun.tools.javac.comp.Resolve;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.code.Symbol;
/** Subclass to Resolve that overrides collect.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class ResolveWithDeps extends Resolve {
/** The dependency database
*/
protected Dependencies deps;
protected ResolveWithDeps(Context context) {
super(context);
deps = Dependencies.instance(context);
}
public static void preRegister(Context context) {
context.put(resolveKey, new Context.Factory<Resolve>() {
public Resolve make(Context c) {
Resolve instance = new ResolveWithDeps(c);
c.put(Resolve.class, instance);
return instance;
}
});
}
/** Collect dependencies in the enclosing class
* @param from The enclosing class sym
* @param to The enclosing classes references this sym.
* */
@Override
public void reportDependence(Symbol from, Symbol to) {
// Capture dependencies between the packages.
deps.collect(from.packge().fullname, to.packge().fullname);
}
}

View File

@ -0,0 +1,221 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac.comp;
import com.sun.tools.javac.util.ListBuffer;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URI;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;
import javax.tools.*;
import javax.tools.JavaFileObject.Kind;
/**
* Intercepts reads and writes to the file system to gather
* information about what artifacts are generated.
*
* Traps writes to certain files, if the content written is identical
* to the existing file.
*
* Can also blind out the filemanager from seeing certain files in the file system.
* Necessary to prevent javac from seeing some sources where the source path points.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class SmartFileManager extends ForwardingJavaFileManager<JavaFileManager> {
// Set of sources that can be seen by javac.
Set<URI> visibleSources = new HashSet<URI>();
// Map from modulename:packagename to artifacts.
Map<String,Set<URI>> packageArtifacts = new HashMap<String,Set<URI>>();
// Where to print informational messages.
PrintWriter stdout;
public SmartFileManager(JavaFileManager fileManager) {
super(fileManager);
}
public void setVisibleSources(Set<URI> s) {
visibleSources = s;
}
public void cleanArtifacts() {
packageArtifacts = new HashMap<String,Set<URI>>();
}
public void setLog(PrintWriter pw) {
stdout = pw;
}
public Map<String,Set<URI>> getPackageArtifacts() {
return packageArtifacts;
}
@Override
public Iterable<JavaFileObject> list(Location location,
String packageName,
Set<Kind> kinds,
boolean recurse)
throws IOException
{
// Acquire the list of files.
Iterable<JavaFileObject> files = super.list(location, packageName, kinds, recurse);
if (visibleSources.isEmpty()) {
return files;
}
// Now filter!
ListBuffer<JavaFileObject> filteredFiles = new ListBuffer<JavaFileObject>();
for (JavaFileObject f : files) {
URI uri = f.toUri();
String t = uri.toString();
if (t.startsWith("jar:")
|| t.endsWith(".class")
|| visibleSources.contains(uri))
{
filteredFiles.add(f);
}
}
return filteredFiles;
}
@Override
public boolean hasLocation(Location location) {
return super.hasLocation(location);
}
@Override
public JavaFileObject getJavaFileForInput(Location location,
String className,
Kind kind)
throws IOException
{
JavaFileObject file = super.getJavaFileForInput(location, className, kind);
if (file == null || visibleSources.isEmpty()) {
return file;
}
if (visibleSources.contains(file.toUri())) {
return file;
}
return null;
}
@Override
public JavaFileObject getJavaFileForOutput(Location location,
String className,
Kind kind,
FileObject sibling)
throws IOException
{
JavaFileObject file = super.getJavaFileForOutput(location, className, kind, sibling);
if (file == null) return file;
int dp = className.lastIndexOf('.');
String pkg_name = "";
if (dp != -1) {
pkg_name = className.substring(0, dp);
}
// When modules are in use, then the mod_name might be something like "jdk_base"
String mod_name = "";
addArtifact(mod_name+":"+pkg_name, file.toUri());
return file;
}
@Override
public FileObject getFileForInput(Location location,
String packageName,
String relativeName)
throws IOException
{
FileObject file = super.getFileForInput(location, packageName, relativeName);
if (file == null || visibleSources.isEmpty()) {
return file;
}
if (visibleSources.contains(file.toUri())) {
return file;
}
return null;
}
@Override
public FileObject getFileForOutput(Location location,
String packageName,
String relativeName,
FileObject sibling)
throws IOException
{
FileObject file = super.getFileForOutput(location, packageName, relativeName, sibling);
if (file == null) return file;
if (location.equals(StandardLocation.NATIVE_HEADER_OUTPUT) &&
file instanceof JavaFileObject) {
file = new SmartFileObject((JavaFileObject)file, stdout);
packageName = ":" + packageNameFromFileName(relativeName);
}
if (packageName.equals("")) {
packageName = ":";
}
addArtifact(packageName, file.toUri());
return file;
}
private String packageNameFromFileName(String fn) {
StringBuilder sb = new StringBuilder();
int p = fn.indexOf('_'), pp = 0;
while (p != -1) {
if (sb.length() > 0) sb.append('.');
sb.append(fn.substring(pp,p));
if (p == fn.length()-1) break;
pp = p+1;
p = fn.indexOf('_',pp);
}
return sb.toString();
}
@Override
public void flush() throws IOException {
super.flush();
}
@Override
public void close() throws IOException {
super.close();
}
void addArtifact(String pkgName, URI art) {
Set<URI> s = packageArtifacts.get(pkgName);
if (s == null) {
s = new HashSet<URI>();
packageArtifacts.put(pkgName, s);
}
s.add(art);
}
}

View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac.comp;
import java.io.*;
import java.net.URI;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
import javax.tools.JavaFileObject;
/**
* The SmartFileObject will return an outputstream that cache the written data
* and compare the new content with the old content on disk. Only if they differ,
* will the file be updated.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class SmartFileObject implements JavaFileObject {
JavaFileObject file;
PrintWriter stdout;
public SmartFileObject(JavaFileObject r, PrintWriter pw) {
file = r;
stdout = pw;
}
@Override
public boolean equals(Object other) {
return file.equals(other);
}
@Override
public int hashCode() {
return file.hashCode();
}
public Kind getKind() {
return file.getKind();
}
public boolean isNameCompatible(String simpleName, Kind kind) {
return file.isNameCompatible(simpleName, kind);
}
public URI toUri() {
return file.toUri();
}
public String getName() {
return file.getName();
}
public InputStream openInputStream() throws IOException {
return file.openInputStream();
}
public OutputStream openOutputStream() throws IOException {
return file.openOutputStream();
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return file.getCharContent(ignoreEncodingErrors);
}
static String lineseparator = System.getProperty("line.separator");
public Writer openWriter() throws IOException {
StringBuilder s = new StringBuilder();
try (BufferedReader r = new BufferedReader(file.openReader(true))) {
while (r.ready()) {
s.append(r.readLine()+lineseparator);
}
} catch (FileNotFoundException e) {
// Perfectly ok.
}
return new SmartWriter(file, s.toString(), file.getName(), stdout);
}
public long getLastModified() {
return file.getLastModified();
}
public boolean delete() {
return file.delete();
}
public Modifier getAccessLevel() {
return file.getAccessLevel();
}
public NestingKind getNestingKind() {
return file.getNestingKind();
}
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
return file.openReader(ignoreEncodingErrors);
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac.comp;
import java.io.*;
import javax.tools.JavaFileObject;
/**
* The SmartWriter will cache the written data and when the writer is closed,
* then it will compare the cached data with the old_content string.
* If different, then it will write all the new content to the file.
* If not, the file is not touched.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class SmartWriter extends Writer {
String name;
JavaFileObject file;
String oldContent;
StringWriter newContent = new StringWriter();
PrintWriter stdout;
boolean closed;
public SmartWriter(JavaFileObject f, String s, String n, PrintWriter pw) {
name = n;
file = f;
oldContent = s;
newContent = new StringWriter();
stdout = pw;
closed = false;
}
public void write(char[] chars, int i, int i1)
{
newContent.write(chars, i, i1);
}
public void close() throws IOException {
if (closed) return;
closed = true;
String s = newContent.toString();
if (!oldContent.equals(s)) {
int p = file.getName().lastIndexOf(File.separatorChar);
try (Writer writer = file.openWriter()) {
writer.write(s);
}
stdout.println("Writing "+file.getName().substring(p+1));
}
}
public void flush() throws IOException {
}
}

View File

@ -0,0 +1,163 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac.server;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.Stack;
import java.util.concurrent.Future;
/** The compiler pool maintains compiler threads.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class CompilerPool {
// The javac server that created this pool.
private JavacServer javacServer;
// A semaphore protecting the poolsize number of threads.
private Semaphore available;
// The stack of compiler threads.
private Stack<CompilerThread> compilers = new Stack<CompilerThread>();
// And the executor server to spawn threads.
private final ExecutorService executorPool;
// How many requests are active right now?
private int concurrentRequests = 0;
// When was the last request finished?
private long lastRequestFinished = 0;
// The total number of requests to this pool.
private int numRequests = 0;
// Protect access to the three above values.
private static final Object conc = new Object();
/**
* Return the javac server that this pool belongs to.
*/
public JavacServer getJavacServer() {
return javacServer;
}
/**
* Return how many threads are running at this very moment.
*/
public int numActiveRequests()
{
synchronized (conc) {
return concurrentRequests;
}
}
/**
* Return when the last request was finished.
* I.e. the pool has been idle since.
*/
public long lastRequestFinished()
{
synchronized (conc) {
return lastRequestFinished;
}
}
/**
* Up the number of active requests.
*/
public int startRequest() {
int n;
synchronized (conc) {
concurrentRequests++;
numRequests++;
n = numRequests;
}
return n;
}
/**
* Down the number of active requests. Return the current time.
*/
public long stopRequest() {
synchronized (conc) {
concurrentRequests--;
lastRequestFinished = System.currentTimeMillis();
}
return lastRequestFinished;
}
/**
* Create a new compiler pool.
*/
CompilerPool(int poolsize, JavacServer server) {
available = new Semaphore(poolsize, true);
javacServer = server;
executorPool = Executors.newFixedThreadPool(poolsize);
lastRequestFinished = System.currentTimeMillis();
}
/**
* Execute a compiler thread.
*/
public void execute(CompilerThread ct) {
executorPool.execute(ct);
}
/**
* Execute a minor task, for example generating bytecodes and writing them to disk,
* that belong to a major compiler thread task.
*/
public Future<?> executeSubtask(CompilerThread t, Runnable r) {
return executorPool.submit(r);
}
/**
* Shutdown the pool.
*/
public void shutdown() {
executorPool.shutdown();
}
/**
* Acquire a compiler thread from the pool, or block until a thread is available.
* If the pools is empty, create a new thread, but never more than is "available".
*/
public CompilerThread grabCompilerThread() throws InterruptedException {
available.acquire();
if (compilers.empty()) {
return new CompilerThread(this);
}
return compilers.pop();
}
/**
* Return the specified compiler thread to the pool.
*/
public void returnCompilerThread(CompilerThread h) {
compilers.push(h);
available.release();
}
}

View File

@ -0,0 +1,419 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac.server;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.concurrent.Future;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.BaseFileManager;
import com.sun.tools.sjavac.comp.Dependencies;
import com.sun.tools.sjavac.comp.JavaCompilerWithDeps;
import com.sun.tools.sjavac.comp.SmartFileManager;
import com.sun.tools.sjavac.comp.ResolveWithDeps;
/**
* The compiler thread maintains a JavaCompiler instance and
* can receive a request from the client, perform the compilation
* requested and report back the results.
*
* * <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class CompilerThread implements Runnable {
private JavacServer javacServer;
private CompilerPool compilerPool;
private List<Future<?>> subTasks;
// Communicating over this socket.
private Socket socket;
// The necessary classes to do a compilation.
private com.sun.tools.javac.api.JavacTool compiler;
private StandardJavaFileManager fileManager;
private BaseFileManager fileManagerBase;
private SmartFileManager smartFileManager;
private Context context;
// If true, then this thread is serving a request.
private boolean inUse = false;
CompilerThread(CompilerPool cp) {
compilerPool = cp;
javacServer = cp.getJavacServer();
}
/**
* Execute a minor task, for example generating bytecodes and writing them to disk,
* that belong to a major compiler thread task.
*/
public synchronized void executeSubtask(Runnable r) {
subTasks.add(compilerPool.executeSubtask(this, r));
}
/**
* Count the number of active sub tasks.
*/
public synchronized int numActiveSubTasks() {
int c = 0;
for (Future<?> f : subTasks) {
if (!f.isDone() && !f.isCancelled()) {
c++;
}
}
return c;
}
/**
* Use this socket for the upcoming request.
*/
public void setSocket(Socket s) {
socket = s;
}
/**
* Prepare the compiler thread for use. It is not yet started.
* It will be started by the executor service.
*/
public synchronized void use() {
assert(!inUse);
inUse = true;
compiler = com.sun.tools.javac.api.JavacTool.create();
fileManager = compiler.getStandardFileManager(null, null, null);
fileManagerBase = (BaseFileManager)fileManager;
smartFileManager = new SmartFileManager(fileManager);
context = new Context();
context.put(JavaFileManager.class, smartFileManager);
ResolveWithDeps.preRegister(context);
JavaCompilerWithDeps.preRegister(context, this);
subTasks = new ArrayList<Future<?>>();
}
/**
* Prepare the compiler thread for idleness.
*/
public synchronized void unuse() {
assert(inUse);
inUse = false;
compiler = null;
fileManager = null;
fileManagerBase = null;
smartFileManager = null;
context = null;
subTasks = null;
}
/**
* Expect this key on the next line read from the reader.
*/
private static boolean expect(BufferedReader in, String key) throws IOException {
String s = in.readLine();
if (s != null && s.equals(key)) {
return true;
}
return false;
}
// The request identifier, for example GENERATE_NEWBYTECODE
String id = "";
public String currentRequestId() {
return id;
}
PrintWriter stdout;
PrintWriter stderr;
int forcedExitCode = 0;
public void logError(String msg) {
stderr.println(msg);
forcedExitCode = -1;
}
/**
* Invoked by the executor service.
*/
public void run() {
// Unique nr that identifies this request.
int thisRequest = compilerPool.startRequest();
long start = System.currentTimeMillis();
int numClasses = 0;
StringBuilder compiledPkgs = new StringBuilder();
use();
PrintWriter out = null;
try {
javacServer.log("<"+thisRequest+"> Connect from "+socket.getRemoteSocketAddress()+" activethreads="+compilerPool.numActiveRequests());
BufferedReader in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
out = new PrintWriter(new OutputStreamWriter(
socket.getOutputStream()));
if (!expect(in, JavacServer.PROTOCOL_COOKIE_VERSION)) {
javacServer.log("<"+thisRequest+"> Bad protocol from ip "+socket.getRemoteSocketAddress());
return;
}
String cookie = in.readLine();
if (cookie == null || !cookie.equals(""+javacServer.getCookie())) {
javacServer.log("<"+thisRequest+"> Bad cookie from ip "+socket.getRemoteSocketAddress());
return;
}
if (!expect(in, JavacServer.PROTOCOL_CWD)) {
return;
}
String cwd = in.readLine();
if (cwd == null)
return;
if (!expect(in, JavacServer.PROTOCOL_ID)) {
return;
}
id = in.readLine();
if (id == null)
return;
if (!expect(in, JavacServer.PROTOCOL_ARGS)) {
return;
}
ArrayList<String> the_options = new ArrayList<String>();
ArrayList<File> the_classes = new ArrayList<File>();
Iterable<File> path = Arrays.<File> asList(new File(cwd));
for (;;) {
String l = in.readLine();
if (l == null)
return;
if (l.equals(JavacServer.PROTOCOL_SOURCES_TO_COMPILE))
break;
if (l.startsWith("--server:"))
continue;
if (!l.startsWith("-") && l.endsWith(".java")) {
the_classes.add(new File(l));
numClasses++;
} else {
the_options.add(l);
}
continue;
}
// Load sources to compile
Set<URI> sourcesToCompile = new HashSet<URI>();
for (;;) {
String l = in.readLine();
if (l == null)
return;
if (l.equals(JavacServer.PROTOCOL_VISIBLE_SOURCES))
break;
try {
sourcesToCompile.add(new URI(l));
numClasses++;
} catch (URISyntaxException e) {
return;
}
}
// Load visible sources
Set<URI> visibleSources = new HashSet<URI>();
boolean fix_drive_letter_case = System.getProperty("os.name").toLowerCase().equals("windows");
for (;;) {
String l = in.readLine();
if (l == null)
return;
if (l.equals(JavacServer.PROTOCOL_END))
break;
try {
URI u = new URI(l);
if (fix_drive_letter_case) {
// Make sure the driver letter is lower case.
String s = u.toString();
if (s.startsWith("file:/") &&
Character.isUpperCase(s.charAt(6))) {
u = new URI("file:/"+Character.toLowerCase(s.charAt(6))+s.substring(7));
}
}
visibleSources.add(u);
} catch (URISyntaxException e) {
return;
}
}
// A completed request has been received.
// Now setup the actual compilation....
// First deal with explicit source files on cmdline and in at file.
com.sun.tools.javac.util.ListBuffer<JavaFileObject> compilationUnits =
new com.sun.tools.javac.util.ListBuffer<JavaFileObject>();
for (JavaFileObject i : fileManager.getJavaFileObjectsFromFiles(the_classes)) {
compilationUnits.append(i);
}
// Now deal with sources supplied as source_to_compile.
com.sun.tools.javac.util.ListBuffer<File> sourcesToCompileFiles =
new com.sun.tools.javac.util.ListBuffer<File>();
for (URI u : sourcesToCompile) {
sourcesToCompileFiles.append(new File(u));
}
for (JavaFileObject i : fileManager.getJavaFileObjectsFromFiles(sourcesToCompileFiles)) {
compilationUnits.append(i);
}
// Log the options to be used.
StringBuilder options = new StringBuilder();
for (String s : the_options) {
options.append(">").append(s).append("< ");
}
javacServer.log(id+" <"+thisRequest+"> options "+options.toString());
forcedExitCode = 0;
// Create a new logger.
StringWriter stdoutLog = new StringWriter();
StringWriter stderrLog = new StringWriter();
stdout = new PrintWriter(stdoutLog);
stderr = new PrintWriter(stderrLog);
com.sun.tools.javac.main.Main.Result rc = com.sun.tools.javac.main.Main.Result.OK;
try {
if (compilationUnits.size() > 0) {
// Bind the new logger to the existing context.
context.put(Log.outKey, stderr);
Log.instance(context).setWriter(Log.WriterKind.NOTICE, stdout);
Log.instance(context).setWriter(Log.WriterKind.WARNING, stderr);
Log.instance(context).setWriter(Log.WriterKind.ERROR, stderr);
// Process the options.
com.sun.tools.javac.api.JavacTool.processOptions(context, smartFileManager, the_options);
fileManagerBase.setContext(context);
smartFileManager.setVisibleSources(visibleSources);
smartFileManager.cleanArtifacts();
smartFileManager.setLog(stdout);
Dependencies.instance(context).reset();
com.sun.tools.javac.main.Main ccompiler = new com.sun.tools.javac.main.Main("javacTask", stderr);
String[] aa = the_options.toArray(new String[0]);
// Do the compilation!
rc = ccompiler.compile(aa, context, compilationUnits.toList(), null);
while (numActiveSubTasks()>0) {
try { Thread.sleep(1000); } catch (InterruptedException e) { }
}
smartFileManager.flush();
}
} catch (Exception e) {
stderr.println(e.getMessage());
forcedExitCode = -1;
}
// Send the response..
out.println(JavacServer.PROTOCOL_STDOUT);
out.print(stdoutLog);
out.println(JavacServer.PROTOCOL_STDERR);
out.print(stderrLog);
// The compilation is complete! And errors will have already been printed on out!
out.println(JavacServer.PROTOCOL_PACKAGE_ARTIFACTS);
Map<String,Set<URI>> pa = smartFileManager.getPackageArtifacts();
for (String aPkgName : pa.keySet()) {
out.println("+"+aPkgName);
Set<URI> as = pa.get(aPkgName);
for (URI a : as) {
out.println(" "+a.toString());
}
}
Dependencies deps = Dependencies.instance(context);
out.println(JavacServer.PROTOCOL_PACKAGE_DEPENDENCIES);
Map<String,Set<String>> pd = deps.getDependencies();
for (String aPkgName : pd.keySet()) {
out.println("+"+aPkgName);
Set<String> ds = pd.get(aPkgName);
// Everything depends on java.lang
if (!ds.contains(":java.lang")) ds.add(":java.lang");
for (String d : ds) {
out.println(" "+d);
}
}
out.println(JavacServer.PROTOCOL_PACKAGE_PUBLIC_APIS);
Map<String,String> pp = deps.getPubapis();
for (String aPkgName : pp.keySet()) {
out.println("+"+aPkgName);
String ps = pp.get(aPkgName);
// getPubapis added a space to each line!
out.println(ps);
compiledPkgs.append(aPkgName+" ");
}
out.println(JavacServer.PROTOCOL_SYSINFO);
out.println("num_cores=" + Runtime.getRuntime().availableProcessors());
out.println("max_memory=" + Runtime.getRuntime().maxMemory());
out.println(JavacServer.PROTOCOL_RETURN_CODE);
// Errors from sjavac that affect compilation status!
int rcv = rc.exitCode;
if (rcv == 0 && forcedExitCode != 0) {
rcv = forcedExitCode;
}
out.println("" + rcv);
out.println(JavacServer.PROTOCOL_END);
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) out.close();
if (!socket.isClosed()) {
socket.close();
}
socket = null;
} catch (Exception e) {
javacServer.log("ERROR "+e);
e.printStackTrace();
}
compilerPool.stopRequest();
long duration = System.currentTimeMillis()-start;
javacServer.addBuildTime(duration);
float classpersec = ((float)numClasses)*(((float)1000.0)/((float)duration));
javacServer.log(id+" <"+thisRequest+"> "+compiledPkgs+" duration " + duration+ " ms num_classes="+numClasses+
" classpersec="+classpersec+" subtasks="+subTasks.size());
javacServer.flushLog();
unuse();
compilerPool.returnCompilerThread(this);
}
}
}

View File

@ -0,0 +1,751 @@
/*
* Copyright (c) 2011-2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac.server;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.FileNotFoundException;
import java.net.URI;
import java.util.HashSet;
import java.util.Set;
import java.util.HashMap;
import java.util.Map;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Random;
import com.sun.tools.sjavac.Util;
import com.sun.tools.sjavac.ProblemException;
import java.io.*;
import java.util.*;
/**
* The JavacServer class contains methods both to setup a server that responds to requests and methods to connect to this server.
*
* <p><b>This is NOT part of any supported API. If you write code that depends on this, you do so at your own risk. This code and its internal interfaces are
* subject to change or deletion without notice.</b></p>
*/
public class JavacServer {
// Responding to this tcp/ip port on localhost.
private final ServerSocket serverSocket;
// The secret cookie shared between server and client through the port file.
private final long myCookie;
// When the server was started.
private long serverStart;
// Accumulated build time for all requests, not counting idle time.
private long totalBuildTime;
// The javac server specific log file.
PrintWriter theLog;
// The compiler pool that maintains the compiler threads.
CompilerPool compilerPool;
// For the client, all port files fetched, one per started javac server.
// Though usually only one javac server is started by a client.
private static Map<String, PortFile> allPortFiles;
private static Map<String, Long> maxServerMemory;
final static int ERROR_FATAL = -1;
final static int ERROR_BUT_TRY_AGAIN = -4712;
final static String PROTOCOL_COOKIE_VERSION = "----THE-COOKIE-V2----";
final static String PROTOCOL_CWD = "----THE-CWD----";
final static String PROTOCOL_ID = "----THE-ID----";
final static String PROTOCOL_ARGS = "----THE-ARGS----";
final static String PROTOCOL_SOURCES_TO_COMPILE = "----THE-SOURCES-TO-COMPILE----";
final static String PROTOCOL_VISIBLE_SOURCES = "----THE-VISIBLE-SOURCES----";
final static String PROTOCOL_END = "----THE-END----";
final static String PROTOCOL_STDOUT = "----THE-STDOUT----";
final static String PROTOCOL_STDERR = "----THE-STDERR----";
final static String PROTOCOL_PACKAGE_ARTIFACTS = "----THE-PACKAGE_ARTIFACTS----";
final static String PROTOCOL_PACKAGE_DEPENDENCIES = "----THE-PACKAGE_DEPENDENCIES----";
final static String PROTOCOL_PACKAGE_PUBLIC_APIS = "----THE-PACKAGE-PUBLIC-APIS----";
final static String PROTOCOL_SYSINFO = "----THE-SYSINFO----";
final static String PROTOCOL_RETURN_CODE = "----THE-RETURN-CODE----";
// Check if the portfile is gone, every 5 seconds.
static int CHECK_PORTFILE_INTERVAL = 5;
// Wait 2 seconds for response, before giving up on javac server.
static int CONNECTION_TIMEOUT = 2;
static int WAIT_BETWEEN_CONNECT_ATTEMPTS = 1;
static int MAX_NUM_CONNECT_ATTEMPTS = 3;
/**
* Acquire the port file. Synchronized since several threads inside an smart javac wrapper client acquires the same port file at the same time.
*/
private static synchronized PortFile getPortFile(String filename) throws FileNotFoundException {
if (allPortFiles == null) {
allPortFiles = new HashMap<String, PortFile>();
}
PortFile pf = allPortFiles.get(filename);
if (pf == null) {
pf = new PortFile(filename);
allPortFiles.put(filename, pf);
}
return pf;
}
/**
* Get the cookie used for this server.
*/
long getCookie() {
return myCookie;
}
/**
* Get the port used for this server.
*/
int getPort() {
return serverSocket.getLocalPort();
}
/**
* Sum up the total build time for this javac server.
*/
public void addBuildTime(long inc) {
totalBuildTime += inc;
}
/**
* Log this message.
*/
public void log(String msg) {
if (theLog != null) {
theLog.println(msg);
} else {
System.err.println(msg);
}
}
/**
* Make sure the log is flushed.
*/
public void flushLog() {
if (theLog != null) {
theLog.flush();
}
}
/**
* Start a server using a settings string. Typically: "--startserver:portfile=/tmp/myserver,poolsize=3" and the string "portfile=/tmp/myserver,poolsize=3"
* is sent as the settings parameter. Returns 0 on success, -1 on failure.
*/
public static int startServer(String settings, PrintStream err) {
try {
String portfile = Util.extractStringOption("portfile", settings);
// The log file collects more javac server specific log information.
String logfile = Util.extractStringOption("logfile", settings);
// The stdouterr file collects all the System.out and System.err writes to disk.
String stdouterrfile = Util.extractStringOption("stdouterrfile", settings);
// We could perhaps use System.setOut and setErr here.
// But for the moment we rely on the client to spawn a shell where stdout
// and stderr are redirected already.
// The pool size is a limit the number of concurrent compiler threads used.
// The server might use less than these to avoid memory problems.
int poolsize = Util.extractIntOption("poolsize", settings);
if (poolsize <= 0) {
// If not set, default to the number of cores.
poolsize = Runtime.getRuntime().availableProcessors();
}
// How many seconds of inactivity will the server accept before quitting?
int keepalive = Util.extractIntOption("keepalive", settings);
if (keepalive <= 0) {
keepalive = 120;
}
// The port file is locked and the server port and cookie is written into it.
PortFile portFile = getPortFile(portfile);
JavacServer s;
synchronized (portFile) {
portFile.lock();
portFile.getValues();
if (portFile.containsPortInfo()) {
err.println("Javac server not started because portfile exists!");
portFile.unlock();
return -1;
}
s = new JavacServer(poolsize, logfile);
portFile.setValues(s.getPort(), s.getCookie());
portFile.unlock();
}
// Run the server. Will delete the port file when shutting down.
// It will shut down automatically when no new requests have come in
// during the last 125 seconds.
s.run(portFile, err, keepalive);
// The run loop for the server has exited.
return 0;
} catch (Exception e) {
e.printStackTrace(err);
return -1;
}
}
/**
* Dispatch a compilation request to a javac server.
*
* @param args are the command line args to javac and is allowed to contain source files, @file and other command line options to javac.
*
* The generated classes, h files and other artifacts from the javac invocation are stored by the javac server to disk.
*
* @param sources_to_compile The sources to compile.
*
* @param visibleSources If visible sources has a non zero size, then visible_sources are the only files in the file system that the javac server can see!
* (Sources to compile are always visible.) The visible sources are those supplied by the (filtered) -sourcepath
*
* @param visibleClasses If visible classes for a specific root/jar has a non zero size, then visible_classes are the only class files that the javac server
* can see, in that root/jar. It maps from a classpath root or a jar file to the set of visible classes for that root/jar.
*
* The server return meta data about the build in the following parameters.
* @param package_artifacts, map from package name to set of created artifacts for that package.
* @param package_dependencies, map from package name to set of packages that it depends upon.
* @param package_pubapis, map from package name to unique string identifying its pub api.
*/
public static int useServer(String settings, String[] args,
Set<URI> sourcesToCompile,
Set<URI> visibleSources,
Map<URI, Set<String>> visibleClasses,
Map<String, Set<URI>> packageArtifacts,
Map<String, Set<String>> packageDependencies,
Map<String, String> packagePubapis,
SysInfo sysinfo,
PrintStream out,
PrintStream err) {
try {
// The id can perhaps be used in the future by the javac server to reuse the
// JavaCompiler instance for several compiles using the same id.
String id = Util.extractStringOption("id", settings);
String portfile = Util.extractStringOption("portfile", settings);
String logfile = Util.extractStringOption("logfile", settings);
String stdouterrfile = Util.extractStringOption("stdouterrfile", settings);
String background = Util.extractStringOption("background", settings);
if (background == null || !background.equals("false")) {
background = "true";
}
// The sjavac option specifies how the server part of sjavac is spawned.
// If you have the experimental sjavac in your path, you are done. If not, you have
// to point to a com.sun.tools.sjavac.Main that supports --startserver
// for example by setting: sjavac=java%20-jar%20...javac.jar%com.sun.tools.sjavac.Main
String sjavac = Util.extractStringOption("sjavac", settings);
int poolsize = Util.extractIntOption("poolsize", settings);
int keepalive = Util.extractIntOption("keepalive", settings);
if (keepalive <= 0) {
// Default keepalive for server is 120 seconds.
// I.e. it will accept 120 seconds of inactivity before quitting.
keepalive = 120;
}
if (portfile == null) {
err.println("No portfile was specified!");
return -1;
}
if (logfile == null) {
logfile = portfile + ".javaclog";
}
if (stdouterrfile == null) {
stdouterrfile = portfile + ".stdouterr";
}
// Default to sjavac and hope it is in the path.
if (sjavac == null) {
sjavac = "sjavac";
}
int attempts = 0;
int rc = -1;
do {
PortFile port_file = getPortFile(portfile);
synchronized (port_file) {
port_file.lock();
port_file.getValues();
port_file.unlock();
}
if (!port_file.containsPortInfo()) {
String cmd = fork(sjavac, port_file.getFilename(), logfile, poolsize, keepalive, err, stdouterrfile, background);
if (background.equals("true") && !port_file.waitForValidValues()) {
// Ouch the server did not start! Lets print its stdouterrfile and the command used.
printFailedAttempt(cmd, stdouterrfile, err);
// And give up.
return -1;
}
}
rc = connectAndCompile(port_file, id, args, sourcesToCompile, visibleSources,
packageArtifacts, packageDependencies, packagePubapis, sysinfo,
out, err);
// Try again until we manage to connect. Any error after that
// will cause the compilation to fail.
if (rc == ERROR_BUT_TRY_AGAIN) {
// We could not connect to the server. Try again.
attempts++;
try {
Thread.sleep(WAIT_BETWEEN_CONNECT_ATTEMPTS);
} catch (InterruptedException e) {
}
}
} while (rc == ERROR_BUT_TRY_AGAIN && attempts < MAX_NUM_CONNECT_ATTEMPTS);
return rc;
} catch (Exception e) {
e.printStackTrace(err);
return -1;
}
}
private static void printFailedAttempt(String cmd, String f, PrintStream err) {
err.println("---- Failed to start javac server with this command -----");
err.println(cmd);
try {
BufferedReader in = new BufferedReader(new FileReader(f));
err.println("---- stdout/stderr output from attempt to start javac server -----");
for (;;) {
String l = in.readLine();
if (l == null) {
break;
}
err.println(l);
}
err.println("------------------------------------------------------------------");
} catch (Exception e) {
err.println("The stdout/stderr output in file " + f + " does not exist and the server did not start.");
}
}
/**
* Spawn the server instance.
*/
private JavacServer(int poolSize, String logfile) throws IOException {
serverStart = System.currentTimeMillis();
// Create a server socket on a random port that is bound to the localhost/127.0.0.1 interface.
// I.e only local processes can connect to this port.
serverSocket = new ServerSocket(0, 128, InetAddress.getByName(null));
compilerPool = new CompilerPool(poolSize, this);
Random rnd = new Random();
myCookie = rnd.nextLong();
theLog = new PrintWriter(logfile);
log("Javac server started. port=" + getPort() + " date=" + (new java.util.Date()) + " with poolsize=" + poolSize);
flushLog();
}
/**
* Fork a background process. Returns the command line used that can be printed if something failed.
*/
private static String fork(String sjavac, String portfile, String logfile, int poolsize, int keepalive,
final PrintStream err, String stdouterrfile, String background)
throws IOException, ProblemException {
if (stdouterrfile != null && stdouterrfile.trim().equals("")) {
stdouterrfile = null;
}
final String startserver = "--startserver:portfile=" + portfile + ",logfile=" + logfile + ",stdouterrfile=" + stdouterrfile + ",poolsize=" + poolsize + ",keepalive="+ keepalive;
if (background.equals("true")) {
sjavac += "%20" + startserver;
sjavac = sjavac.replaceAll("%20", " ");
sjavac = sjavac.replaceAll("%2C", ",");
// If the java/sh/cmd launcher fails the failure will be captured by stdouterr because of the redirection here.
String[] cmd = {"/bin/sh", "-c", sjavac + " >> " + stdouterrfile + " 2>&1"};
if (!(new File("/bin/sh")).canExecute()) {
ArrayList<String> wincmd = new ArrayList<String>();
wincmd.add("cmd");
wincmd.add("/c");
wincmd.add("start");
wincmd.add("cmd");
wincmd.add("/c");
wincmd.add(sjavac + " >> " + stdouterrfile + " 2>&1");
cmd = wincmd.toArray(new String[wincmd.size()]);
}
Process pp = null;
try {
pp = Runtime.getRuntime().exec(cmd);
} catch (Exception e) {
e.printStackTrace(err);
e.printStackTrace(new PrintWriter(stdouterrfile));
}
StringBuilder rs = new StringBuilder();
for (String s : cmd) {
rs.append(s + " ");
}
return rs.toString();
}
// Do not spawn a background server, instead run it within the same JVM.
Thread t = new Thread() {
@Override
public void run() {
try {
JavacServer.startServer(startserver, err);
} catch (Throwable t) {
t.printStackTrace(err);
}
}
};
t.start();
return "";
}
/**
* Expect this key on the next line read from the reader.
*/
private static boolean expect(BufferedReader in, String key) throws IOException {
String s = in.readLine();
if (s != null && s.equals(key)) {
return true;
}
return false;
}
/**
* Make a request to the server only to get the maximum possible heap size to use for compilations.
*
* @param port_file The port file used to synchronize creation of this server.
* @param id The identify of the compilation.
* @param out Standard out information.
* @param err Standard err information.
* @return The maximum heap size in bytes.
*/
public static SysInfo connectGetSysInfo(String serverSettings, PrintStream out, PrintStream err) {
SysInfo sysinfo = new SysInfo(-1, -1);
String id = Util.extractStringOption("id", serverSettings);
String portfile = Util.extractStringOption("portfile", serverSettings);
try {
PortFile pf = getPortFile(portfile);
useServer(serverSettings, new String[0],
new HashSet<URI>(),
new HashSet<URI>(),
new HashMap<URI, Set<String>>(),
new HashMap<String, Set<URI>>(),
new HashMap<String, Set<String>>(),
new HashMap<String, String>(),
sysinfo, out, err);
} catch (Exception e) {
e.printStackTrace(err);
}
return sysinfo;
}
/**
* Connect and compile using the javac server settings and the args. When using more advanced features, the sources_to_compile and visible_sources are
* supplied to the server and meta data is returned in package_artifacts, package_dependencies and package_pubapis.
*/
private static int connectAndCompile(PortFile portFile, String id, String[] args,
Set<URI> sourcesToCompile,
Set<URI> visibleSources,
Map<String, Set<URI>> packageArtifacts,
Map<String, Set<String>> packageDependencies,
Map<String, String> packagePublicApis,
SysInfo sysinfo,
PrintStream out,
PrintStream err) {
int rc = -3;
try {
int port = portFile.getPort();
if (port == 0) {
return ERROR_BUT_TRY_AGAIN;
}
long cookie = portFile.getCookie();
// Acquire the localhost/127.0.0.1 address.
InetAddress addr = InetAddress.getByName(null);
SocketAddress sockaddr = new InetSocketAddress(addr, port);
Socket sock = new Socket();
int timeoutMs = CONNECTION_TIMEOUT * 1000;
try {
sock.connect(sockaddr, timeoutMs);
} catch (java.net.ConnectException e) {
err.println("Could not connect to javac server found in portfile: " + portFile.getFilename() + " " + e);
return ERROR_BUT_TRY_AGAIN;
}
if (!sock.isConnected()) {
err.println("Could not connect to javac server found in portfile: " + portFile.getFilename());
return ERROR_BUT_TRY_AGAIN;
}
BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
PrintWriter sockout = new PrintWriter(sock.getOutputStream());
sockout.println(PROTOCOL_COOKIE_VERSION);
sockout.println("" + cookie);
sockout.println(PROTOCOL_CWD);
sockout.println(System.getProperty("user.dir"));
sockout.println(PROTOCOL_ID);
sockout.println(id);
sockout.println(PROTOCOL_ARGS);
for (String s : args) {
StringBuffer buf = new StringBuffer();
String[] paths = s.split(File.pathSeparator);
int c = 0;
for (String path : paths) {
File f = new File(path);
if (f.isFile() || f.isDirectory()) {
buf.append(f.getAbsolutePath());
c++;
if (c < paths.length) {
buf.append(File.pathSeparator);
}
} else {
buf = new StringBuffer(s);
break;
}
}
sockout.println(buf.toString());
}
sockout.println(PROTOCOL_SOURCES_TO_COMPILE);
for (URI uri : sourcesToCompile) {
sockout.println(uri.toString());
}
sockout.println(PROTOCOL_VISIBLE_SOURCES);
for (URI uri : visibleSources) {
sockout.println(uri.toString());
}
sockout.println(PROTOCOL_END);
sockout.flush();
StringBuffer stdout = new StringBuffer();
StringBuffer stderr = new StringBuffer();
if (!expect(in, PROTOCOL_STDOUT)) {
return ERROR_FATAL;
}
// Load stdout
for (;;) {
String l = in.readLine();
if (l == null) {
return ERROR_FATAL;
}
if (l.equals(PROTOCOL_STDERR)) {
break;
}
stdout.append(l);
stdout.append('\n');
}
// Load stderr
for (;;) {
String l = in.readLine();
if (l == null) {
return ERROR_FATAL;
}
if (l.equals(PROTOCOL_PACKAGE_ARTIFACTS)) {
break;
}
stderr.append(l);
stderr.append('\n');
}
// Load the package artifacts
Set<URI> lastUriSet = null;
for (;;) {
String l = in.readLine();
if (l == null) {
return ERROR_FATAL;
}
if (l.equals(PROTOCOL_PACKAGE_DEPENDENCIES)) {
break;
}
if (l.length() > 1 && l.charAt(0) == '+') {
String pkg = l.substring(1);
lastUriSet = new HashSet<URI>();
packageArtifacts.put(pkg, lastUriSet);
} else if (l.length() > 1 && lastUriSet != null) {
lastUriSet.add(new URI(l.substring(1)));
}
}
// Load package dependencies
Set<String> lastPackageSet = null;
for (;;) {
String l = in.readLine();
if (l == null) {
return ERROR_FATAL;
}
if (l.equals(PROTOCOL_PACKAGE_PUBLIC_APIS)) {
break;
}
if (l.length() > 1 && l.charAt(0) == '+') {
String pkg = l.substring(1);
lastPackageSet = new HashSet<String>();
packageDependencies.put(pkg, lastPackageSet);
} else if (l.length() > 1 && lastPackageSet != null) {
lastPackageSet.add(l.substring(1));
}
}
// Load package pubapis
Map<String, StringBuffer> tmp = new HashMap<String, StringBuffer>();
StringBuffer lastPublicApi = null;
for (;;) {
String l = in.readLine();
if (l == null) {
return ERROR_FATAL;
}
if (l.equals(PROTOCOL_SYSINFO)) {
break;
}
if (l.length() > 1 && l.charAt(0) == '+') {
String pkg = l.substring(1);
lastPublicApi = new StringBuffer();
tmp.put(pkg, lastPublicApi);
} else if (l.length() > 1 && lastPublicApi != null) {
lastPublicApi.append(l.substring(1));
lastPublicApi.append("\n");
}
}
for (String p : tmp.keySet()) {
assert (packagePublicApis.get(p) == null);
String api = tmp.get(p).toString();
packagePublicApis.put(p, api);
}
// Now reading the max memory possible.
for (;;) {
String l = in.readLine();
if (l == null) {
return ERROR_FATAL;
}
if (l.equals(PROTOCOL_RETURN_CODE)) {
break;
}
if (l.startsWith("num_cores=") && sysinfo != null) {
sysinfo.numCores = Integer.parseInt(l.substring(10));
}
if (l.startsWith("max_memory=") && sysinfo != null) {
sysinfo.maxMemory = Long.parseLong(l.substring(11));
}
}
String l = in.readLine();
if (l == null) {
err.println("No return value from the server!");
return ERROR_FATAL;
}
rc = Integer.parseInt(l);
out.print(stdout);
err.print(stderr);
} catch (Exception e) {
e.printStackTrace(err);
}
return rc;
}
/**
* Run the server thread until it exits. Either because of inactivity or because the port file has been deleted by someone else, or overtaken by some other
* javac server.
*/
private void run(PortFile portFile, PrintStream err, int keepalive) {
boolean fileDeleted = false;
long timeSinceLastCompile;
try {
// Every 5 second (check_portfile_interval) we test if the portfile has disappeared => quit
// Or if the last request was finished more than 125 seconds ago => quit
// 125 = seconds_of_inactivity_before_shutdown+check_portfile_interval
serverSocket.setSoTimeout(CHECK_PORTFILE_INTERVAL*1000);
for (;;) {
try {
Socket s = serverSocket.accept();
CompilerThread ct = compilerPool.grabCompilerThread();
ct.setSocket(s);
compilerPool.execute(ct);
flushLog();
} catch (java.net.SocketTimeoutException e) {
if (compilerPool.numActiveRequests() > 0) {
// Never quit while there are active requests!
continue;
}
// If this is the timeout after the portfile
// has been deleted by us. Then we truly stop.
if (fileDeleted) {
log("Quitting because of "+(keepalive+CHECK_PORTFILE_INTERVAL)+" seconds of inactivity!");
break;
}
// Check if the portfile is still there.
if (!portFile.exists()) {
// Time to quit because the portfile was deleted by another
// process, probably by the makefile that is done building.
log("Quitting because portfile was deleted!");
flushLog();
break;
}
// Check if portfile.stop is still there.
if (portFile.markedForStop()) {
// Time to quit because another process touched the file
// server.port.stop to signal that the server should stop.
// This is necessary on some operating systems that lock
// the port file hard!
log("Quitting because a portfile.stop file was found!");
portFile.delete();
flushLog();
break;
}
// Does the portfile still point to me?
if (!portFile.stillMyValues()) {
// Time to quit because another build has started.
log("Quitting because portfile is now owned by another javac server!");
flushLog();
break;
}
// Check how long since the last request finished.
long diff = System.currentTimeMillis() - compilerPool.lastRequestFinished();
if (diff < keepalive * 1000) {
// Do not quit if we have waited less than 120 seconds.
continue;
}
// Ok, time to quit because of inactivity. Perhaps the build
// was killed and the portfile not cleaned up properly.
portFile.delete();
fileDeleted = true;
log("" + keepalive + " seconds of inactivity quitting in "
+ CHECK_PORTFILE_INTERVAL + " seconds!");
flushLog();
// Now we have a second 5 second grace
// period where javac remote requests
// that have loaded the data from the
// recently deleted portfile can connect
// and complete their requests.
}
}
} catch (Exception e) {
e.printStackTrace(err);
e.printStackTrace(theLog);
flushLog();
} finally {
compilerPool.shutdown();
}
long realTime = System.currentTimeMillis() - serverStart;
log("Shutting down.");
log("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms");
flushLog();
}
public static void cleanup(String... args) {
String settings = Util.findServerSettings(args);
if (settings == null) return;
String portfile = Util.extractStringOption("portfile", settings);
String background = Util.extractStringOption("background", settings);
if (background != null && background.equals("false")) {
// If the server runs within this jvm, then delete the portfile,
// since this jvm is about to exit soon.
File f = new File(portfile);
f.delete();
}
}
}

View File

@ -0,0 +1,259 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac.server;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.FileLockInterruptionException;
import com.sun.tools.sjavac.Log;
/**
* The PortFile class mediates access to a short binary file containing the tcp/ip port (for the localhost)
* and a cookie necessary for the server answering on that port. The file can be locked using file system
* primitives to avoid race conditions when several javac clients are started at the same. Note that file
* system locking is not always supported on a all operating systems and/or file systems.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
class PortFile {
// Port file format:
// byte ordering: high byte first = big endian
// Magic nr, 4 byte int, first in file.
private final static int magicNr = 0x1174;
// Followed by a 4 byte int, with the port nr.
// Followed by a 8 byte long, with cookie nr.
private String filename;
private File file;
private File stopFile;
private RandomAccessFile rwfile;
private FileChannel channel;
private FileLock lock;
private boolean containsPortInfo;
private int serverPort;
private long serverCookie;
private int myServerPort;
private long myServerCookie;
/**
* Create a new portfile.
* @param filename is the path to the file.
*/
public PortFile(String fn) throws FileNotFoundException
{
filename = fn;
file = new File(filename);
stopFile = new File(filename+".stop");
rwfile = new RandomAccessFile(file, "rw");
// The rwfile should only be readable by the owner of the process
// and no other! How do we do that on a RandomAccessFile?
channel = rwfile.getChannel();
containsPortInfo = false;
lock = null;
}
/**
* Lock the port file.
*/
void lock() throws IOException {
lock = channel.lock();
}
/**
* Read the values from the port file in the file system.
* Expects the port file to be locked.
*/
public void getValues() {
containsPortInfo = false;
if (lock == null) {
// Not locked, remain ignorant about port file contents.
return;
}
try {
if (rwfile.length()>0) {
rwfile.seek(0);
int nr = rwfile.readInt();
serverPort = rwfile.readInt();
serverCookie = rwfile.readLong();
if (nr == magicNr) {
containsPortInfo = true;
} else {
containsPortInfo = false;
}
}
} catch (Exception e) {
containsPortInfo = false;
}
}
/**
* Did the locking and getValues succeed?
*/
public boolean containsPortInfo() {
return containsPortInfo;
}
/**
* If so, then we can acquire the tcp/ip port on localhost.
*/
public int getPort() {
assert(containsPortInfo);
return serverPort;
}
/**
* If so, then we can acquire the server cookie.
*/
public long getCookie() {
assert(containsPortInfo);
return serverCookie;
}
/**
* Store the values into the locked port file.
*/
public void setValues(int port, long cookie) throws IOException {
assert(lock != null);
rwfile.seek(0);
// Write the magic nr that identifes a port file.
rwfile.writeInt(magicNr);
rwfile.writeInt(port);
rwfile.writeLong(cookie);
myServerPort = port;
myServerCookie = cookie;
}
/**
* Delete the port file.
*/
public void delete() throws IOException {
// Access to file must be closed before deleting.
rwfile.close();
// Now delete.
file.delete();
}
/**
* Is the port file still there?
*/
public boolean exists() throws IOException {
return file.exists();
}
/**
* Is a stop file there?
*/
public boolean markedForStop() throws IOException {
if (stopFile.exists()) {
try {
stopFile.delete();
} catch (Exception e)
{}
return true;
}
return false;
}
/**
* Unlock the port file.
*/
public void unlock() throws IOException {
assert(lock != null);
lock.release();
lock = null;
}
/**
* Wait for the port file to contain values that look valid.
* Return true, if a-ok, false if the valid values did not materialize within 5 seconds.
*/
public synchronized boolean waitForValidValues() throws IOException, FileNotFoundException {
for (int tries = 0; tries < 50; tries++) {
lock();
getValues();
unlock();
if (containsPortInfo) {
Log.debug("Found valid values in port file after waiting "+(tries*100)+"ms");
return true;
}
try {
Thread.sleep(100);
} catch (InterruptedException e)
{}
}
Log.debug("Gave up waiting for valid values in port file");
return false;
}
/**
* Check if the portfile still contains my values, assuming that I am the server.
*/
public synchronized boolean stillMyValues() throws IOException, FileNotFoundException {
for (;;) {
try {
lock();
getValues();
unlock();
if (containsPortInfo) {
if (serverPort == myServerPort &&
serverCookie == myServerCookie) {
// Everything is ok.
return true;
}
// Someone has overwritten the port file.
// Probably another javac server, lets quit.
return false;
}
// Something else is wrong with the portfile. Lets quit.
return false;
} catch (FileLockInterruptionException e) {
continue;
}
catch (ClosedChannelException e) {
// The channel has been closed since sjavac is exiting.
return false;
}
}
}
/**
* Return the name of the port file.
*/
public String getFilename() {
return filename;
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* A utility class used to report information about the system
* where the javac server is running.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
package com.sun.tools.sjavac.server;
public class SysInfo {
public int numCores;
public long maxMemory;
public SysInfo(int nc, long mm) {
numCores = nc;
maxMemory = mm;
}
}

View File

@ -0,0 +1,590 @@
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Test all aspects of sjavac.
*
* @bug 8004658
* @summary Add internal smart javac wrapper to solve JEP 139
*
* @run main SJavac
*/
import java.util.*;
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.nio.charset.*;
import com.sun.tools.sjavac.Main;
public
class SJavac {
public static void main(String... args) throws Exception {
URL url = SJavac.class.getClassLoader().getResource("com/sun/tools/sjavac/Main.class");
if (url == null) {
// No sjavac in the classpath.
System.out.println("pass by default");
return;
}
SJavac s = new SJavac();
s.test();
}
FileSystem defaultfs = FileSystems.getDefault();
// Where to put generated sources that will
// test aspects of sjavac, ie JTWork/scratch/gensrc
Path gensrc;
// More gensrc dirs are used to test merging of serveral source roots.
Path gensrc2;
Path gensrc3;
// Where to put compiled classes.
Path bin;
// Where to put c-header files.
Path headers;
// The sjavac compiler.
Main main = new Main();
// Remember the previous bin and headers state here.
Map<String,Long> previous_bin_state;
Map<String,Long> previous_headers_state;
public void test() throws Exception {
gensrc = defaultfs.getPath("gensrc");
gensrc2 = defaultfs.getPath("gensrc2");
gensrc3 = defaultfs.getPath("gensrc3");
bin = defaultfs.getPath("bin");
headers = defaultfs.getPath("headers");
Files.createDirectory(gensrc);
Files.createDirectory(gensrc2);
Files.createDirectory(gensrc3);
Files.createDirectory(bin);
Files.createDirectory(headers);
initialCompile();
incrementalCompileNoChanges();
incrementalCompileDroppingClasses();
incrementalCompileWithChange();
incrementalCompileDropAllNatives();
incrementalCompileAddNative();
incrementalCompileChangeNative();
compileWithOverrideSource();
compileWithInvisibleSources();
compileCircularSources();
delete(gensrc);
delete(gensrc2);
delete(gensrc3);
delete(bin);
}
void initialCompile() throws Exception {
System.out.println("\nInitial compile of gensrc.");
System.out.println("----------------------------");
populate(gensrc,
"alfa/AINT.java",
"package alfa; public interface AINT { void aint(); }",
"alfa/A.java",
"package alfa; public class A implements AINT { "+
"public final static int DEFINITION = 17; public void aint() { } }",
"alfa/AA.java",
"package alfa;"+
"// A package private class, not contributing to the public api.\n"+
"class AA {"+
" // A properly nested static inner class.\n"+
" static class AAA { }\n"+
" // A properly nested inner class.\n"+
" class AAAA { }\n"+
" Runnable foo() {\n"+
" // A proper anonymous class.\n"+
" return new Runnable() { public void run() { } };\n"+
" }\n"+
" AAA aaa;\n"+
" AAAA aaaa;\n"+
" AAAAA aaaaa;\n"+
"}\n"+
"class AAAAA {\n"+
" // A bad auxiliary class, but no one is referencing it\n"+
" // from outside of this source file, therefore it is ok.\n"+
"}\n",
"beta/BINT.java",
"package beta;public interface BINT { void foo(); }",
"beta/B.java",
"package beta; import alfa.A; public class B {"+
"private int b() { return A.DEFINITION; } native void foo(); }");
compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1",
"--server:portfile=testserver,background=false", "--log=debug");
previous_bin_state = collectState(bin);
previous_headers_state = collectState(headers);
}
void incrementalCompileNoChanges() throws Exception {
System.out.println("\nTesting that no change in sources implies no change in binaries.");
System.out.println("------------------------------------------------------------------");
compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1",
"--server:portfile=testserver,background=false", "--log=debug");
Map<String,Long> new_bin_state = collectState(bin);
verifyEqual(new_bin_state, previous_bin_state);
Map<String,Long> new_headers_state = collectState(headers);
verifyEqual(previous_headers_state, new_headers_state);
}
void incrementalCompileDroppingClasses() throws Exception {
System.out.println("\nTesting that deleting AA.java deletes all");
System.out.println("generated inner class as well as AA.class");
System.out.println("-----------------------------------------");
removeFrom(gensrc, "alfa/AA.java");
compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1",
"--server:portfile=testserver,background=false", "--log=debug");
Map<String,Long> new_bin_state = collectState(bin);
verifyThatFilesHaveBeenRemoved(previous_bin_state, new_bin_state,
"bin/alfa/AA$1.class",
"bin/alfa/AA$AAAA.class",
"bin/alfa/AA$AAA.class",
"bin/alfa/AAAAA.class",
"bin/alfa/AA.class");
previous_bin_state = new_bin_state;
Map<String,Long> new_headers_state = collectState(headers);
verifyEqual(previous_headers_state, new_headers_state);
}
void incrementalCompileWithChange() throws Exception {
System.out.println("\nNow update the A.java file with a new timestamps and");
System.out.println("new final static definition. This should trigger a recompile,");
System.out.println("not only of alfa, but also beta.");
System.out.println("But check that the generated native header was not updated!");
System.out.println("Since we did not modify the native api of B.");
System.out.println("-------------------------------------------------------------");
populate(gensrc,"alfa/A.java",
"package alfa; public class A implements AINT { "+
"public final static int DEFINITION = 18; public void aint() { } private void foo() { } }");
compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1",
"--server:portfile=testserver,background=false", "--log=debug");
Map<String,Long> new_bin_state = collectState(bin);
verifyNewerFiles(previous_bin_state, new_bin_state,
"bin/alfa/A.class",
"bin/alfa/AINT.class",
"bin/beta/B.class",
"bin/beta/BINT.class",
"bin/javac_state");
previous_bin_state = new_bin_state;
Map<String,Long> new_headers_state = collectState(headers);
verifyEqual(new_headers_state, previous_headers_state);
}
void incrementalCompileDropAllNatives() throws Exception {
System.out.println("\nNow update the B.java file with one less native method,");
System.out.println("ie it has no longer any methods!");
System.out.println("Verify that beta_B.h is removed!");
System.out.println("---------------------------------------------------------");
populate(gensrc,"beta/B.java",
"package beta; import alfa.A; public class B {"+
"private int b() { return A.DEFINITION; } }");
compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1",
"--server:portfile=testserver,background=false", "--log=debug");
Map<String,Long> new_bin_state = collectState(bin);
verifyNewerFiles(previous_bin_state, new_bin_state,
"bin/beta/B.class",
"bin/beta/BINT.class",
"bin/javac_state");
previous_bin_state = new_bin_state;
Map<String,Long> new_headers_state = collectState(headers);
verifyThatFilesHaveBeenRemoved(previous_headers_state, new_headers_state,
"headers/beta_B.h");
previous_headers_state = new_headers_state;
}
void incrementalCompileAddNative() throws Exception {
System.out.println("\nNow update the B.java file with a final static annotated with @Native.");
System.out.println("Verify that beta_B.h is added again!");
System.out.println("------------------------------------------------------------------------");
populate(gensrc,"beta/B.java",
"package beta; import alfa.A; public class B {"+
"private int b() { return A.DEFINITION; } "+
"@java.lang.annotation.Native final static int alfa = 42; }");
compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1",
"--server:portfile=testserver,background=false", "--log=debug");
Map<String,Long> new_bin_state = collectState(bin);
verifyNewerFiles(previous_bin_state, new_bin_state,
"bin/beta/B.class",
"bin/beta/BINT.class",
"bin/javac_state");
previous_bin_state = new_bin_state;
Map<String,Long> new_headers_state = collectState(headers);
verifyThatFilesHaveBeenAdded(previous_headers_state, new_headers_state,
"headers/beta_B.h");
previous_headers_state = new_headers_state;
}
void incrementalCompileChangeNative() throws Exception {
System.out.println("\nNow update the B.java file with a new value for the final static"+
" annotated with @Native.");
System.out.println("Verify that beta_B.h is rewritten again!");
System.out.println("-------------------------------------------------------------------");
populate(gensrc,"beta/B.java",
"package beta; import alfa.A; public class B {"+
"private int b() { return A.DEFINITION; } "+
"@java.lang.annotation.Native final static int alfa = 43; }");
compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1",
"--server:portfile=testserver,background=false", "--log=debug");
Map<String,Long> new_bin_state = collectState(bin);
verifyNewerFiles(previous_bin_state, new_bin_state,
"bin/beta/B.class",
"bin/beta/BINT.class",
"bin/javac_state");
previous_bin_state = new_bin_state;
Map<String,Long> new_headers_state = collectState(headers);
verifyNewerFiles(previous_headers_state, new_headers_state,
"headers/beta_B.h");
previous_headers_state = new_headers_state;
}
void compileWithOverrideSource() throws Exception {
System.out.println("\nNow verify that we can override sources to be compiled.");
System.out.println("Compile gensrc and gensrc2. However do not compile broken beta.B in gensrc,");
System.out.println("only compile ok beta.B in gensrc2.");
System.out.println("---------------------------------------------------------------------------");
delete(gensrc);
delete(gensrc2);
delete(bin);
previous_bin_state = collectState(bin);
populate(gensrc,"alfa/A.java",
"package alfa; import beta.B; import gamma.C; public class A { B b; C c; }",
"beta/B.java",
"package beta; public class B { broken",
"gamma/C.java",
"package gamma; public class C { }");
populate(gensrc2,
"beta/B.java",
"package beta; public class B { }");
compile("-x", "beta", "gensrc", "gensrc2", "-d", "bin", "-h", "headers", "-j", "1",
"--server:portfile=testserver,background=false");
Map<String,Long> new_bin_state = collectState(bin);
verifyThatFilesHaveBeenAdded(previous_bin_state, new_bin_state,
"bin/alfa/A.class",
"bin/beta/B.class",
"bin/gamma/C.class",
"bin/javac_state");
System.out.println("----- Compile with exluded beta went well!");
delete(bin);
compileExpectFailure("gensrc", "gensrc2", "-d", "bin", "-h", "headers", "-j", "1",
"--server:portfile=testserver,background=false");
System.out.println("----- Compile without exluded beta failed, as expected! Good!");
delete(bin);
}
void compileWithInvisibleSources() throws Exception {
System.out.println("\nNow verify that we can make sources invisible to linking (sourcepath).");
System.out.println("Compile gensrc and link against gensrc2 and gensrc3, however");
System.out.println("gensrc2 contains broken code in beta.B, thus we must exclude that package");
System.out.println("fortunately gensrc3 contains a proper beta.B.");
System.out.println("------------------------------------------------------------------------");
// Start with a fresh gensrcs and bin.
delete(gensrc);
delete(gensrc2);
delete(gensrc3);
delete(bin);
previous_bin_state = collectState(bin);
populate(gensrc,"alfa/A.java",
"package alfa; import beta.B; import gamma.C; public class A { B b; C c; }");
populate(gensrc2,"beta/B.java",
"package beta; public class B { broken",
"gamma/C.java",
"package gamma; public class C { }");
populate(gensrc3, "beta/B.java",
"package beta; public class B { }");
compile("gensrc", "-x", "beta", "-sourcepath", "gensrc2",
"-sourcepath", "gensrc3", "-d", "bin", "-h", "headers", "-j", "1",
"--server:portfile=testserver,background=false");
System.out.println("The first compile went well!");
Map<String,Long> new_bin_state = collectState(bin);
verifyThatFilesHaveBeenAdded(previous_bin_state, new_bin_state,
"bin/alfa/A.class",
"bin/javac_state");
System.out.println("----- Compile with exluded beta went well!");
delete(bin);
compileExpectFailure("gensrc", "-sourcepath", "gensrc2", "-sourcepath", "gensrc3",
"-d", "bin", "-h", "headers", "-j", "1",
"--server:portfile=testserver,background=false");
System.out.println("----- Compile without exluded beta failed, as expected! Good!");
delete(bin);
}
void compileCircularSources() throws Exception {
System.out.println("\nNow verify that circular sources split on multiple cores can be compiled.");
System.out.println("---------------------------------------------------------------------------");
// Start with a fresh gensrcs and bin.
delete(gensrc);
delete(gensrc2);
delete(gensrc3);
delete(bin);
previous_bin_state = collectState(bin);
populate(gensrc,"alfa/A.java",
"package alfa; public class A { beta.B b; }",
"beta/B.java",
"package beta; public class B { gamma.C c; }",
"gamma/C.java",
"package gamma; public class C { alfa.A a; }");
compile("gensrc", "-d", "bin", "-h", "headers", "-j", "3",
"--server:portfile=testserver,background=false","--log=debug");
Map<String,Long> new_bin_state = collectState(bin);
verifyThatFilesHaveBeenAdded(previous_bin_state, new_bin_state,
"bin/alfa/A.class",
"bin/beta/B.class",
"bin/gamma/C.class",
"bin/javac_state");
delete(bin);
}
void removeFrom(Path dir, String... args) throws IOException {
for (String filename : args) {
Path p = dir.resolve(filename);
Files.delete(p);
}
}
void populate(Path src, String... args) throws IOException {
if (!Files.exists(src)) {
Files.createDirectory(src);
}
String[] a = args;
for (int i = 0; i<a.length; i+=2) {
String filename = a[i];
String content = a[i+1];
Path p = src.resolve(filename);
Files.createDirectories(p.getParent());
PrintWriter out = new PrintWriter(Files.newBufferedWriter(p,
Charset.defaultCharset()));
out.println(content);
out.close();
}
}
void delete(Path root) throws IOException {
if (!Files.exists(root)) return;
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException
{
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException
{
if (e == null) {
if (!dir.equals(root)) Files.delete(dir);
return FileVisitResult.CONTINUE;
} else {
// directory iteration failed
throw e;
}
}
});
}
void compile(String... args) throws Exception {
int rc = main.go(args, System.out, System.err);
if (rc != 0) throw new Exception("Error during compile!");
// Wait a second, to get around the (temporary) problem with
// second resolution in the Java file api. But do not do this
// on windows where the timestamps work.
long in_a_sec = System.currentTimeMillis()+1000;
while (in_a_sec > System.currentTimeMillis()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
void compileExpectFailure(String... args) throws Exception {
int rc = main.go(args, System.out, System.err);
if (rc == 0) throw new Exception("Expected error during compile! Did not fail!");
}
Map<String,Long> collectState(Path dir) throws IOException
{
final Map<String,Long> files = new HashMap<>();
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException
{
files.put(file.toString(),new Long(Files.getLastModifiedTime(file).toMillis()));
return FileVisitResult.CONTINUE;
}
});
return files;
}
void verifyThatFilesHaveBeenRemoved(Map<String,Long> from,
Map<String,Long> to,
String... args) throws Exception {
Set<String> froms = from.keySet();
Set<String> tos = to.keySet();
if (froms.equals(tos)) {
throw new Exception("Expected new state to have fewer files than previous state!");
}
for (String t : tos) {
if (!froms.contains(t)) {
throw new Exception("Expected "+t+" to exist in previous state!");
}
}
for (String f : args) {
f = f.replace("/", File.separator);
if (!froms.contains(f)) {
throw new Exception("Expected "+f+" to exist in previous state!");
}
if (tos.contains(f)) {
throw new Exception("Expected "+f+" to have been removed from the new state!");
}
}
if (froms.size() - args.length != tos.size()) {
throw new Exception("There are more removed files than the expected list!");
}
}
void verifyThatFilesHaveBeenAdded(Map<String,Long> from,
Map<String,Long> to,
String... args) throws Exception {
Set<String> froms = from.keySet();
Set<String> tos = to.keySet();
if (froms.equals(tos)) {
throw new Exception("Expected new state to have more files than previous state!");
}
for (String t : froms) {
if (!tos.contains(t)) {
throw new Exception("Expected "+t+" to exist in new state!");
}
}
for (String f : args) {
f = f.replace("/", File.separator);
if (!tos.contains(f)) {
throw new Exception("Expected "+f+" to have been added to new state!");
}
if (froms.contains(f)) {
throw new Exception("Expected "+f+" to not exist in previous state!");
}
}
if (froms.size() + args.length != tos.size()) {
throw new Exception("There are more added files than the expected list!");
}
}
void verifyNewerFiles(Map<String,Long> from,
Map<String,Long> to,
String... args) throws Exception {
if (!from.keySet().equals(to.keySet())) {
throw new Exception("Expected the set of files to be identical!");
}
Set<String> files = new HashSet<String>();
for (String s : args) {
files.add(s.replace("/", File.separator));
}
for (String fn : from.keySet()) {
long f = from.get(fn);
long t = to.get(fn);
if (files.contains(fn)) {
if (t <= f) {
throw new Exception("Expected "+fn+" to have a more recent timestamp!");
}
} else {
if (t != f) {
throw new Exception("Expected "+fn+" to have the same timestamp!");
}
}
}
}
String print(Map<String,Long> m) {
StringBuilder b = new StringBuilder();
Set<String> keys = m.keySet();
for (String k : keys) {
b.append(k+" "+m.get(k)+"\n");
}
return b.toString();
}
void verifyEqual(Map<String,Long> from, Map<String,Long> to) throws Exception {
if (!from.equals(to)) {
System.out.println("FROM---"+print(from));
System.out.println("TO-----"+print(to));
throw new Exception("The dir should not differ! But it does!");
}
}
}