/**
* Copyright (c) 2012 by JP Moresmau
* This code is made available under the terms of the Eclipse Public License,
* version 1.0 (EPL). See http://www.eclipse.org/legal/epl-v10.html
*/
package net.sf.eclipsefp.haskell.buildwrapper;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import net.sf.eclipsefp.haskell.buildwrapper.types.BWFileInfo;
import net.sf.eclipsefp.haskell.buildwrapper.types.BWProcessInfo;
import net.sf.eclipsefp.haskell.buildwrapper.types.BWProcessManager;
import net.sf.eclipsefp.haskell.buildwrapper.types.BuildFlags;
import net.sf.eclipsefp.haskell.buildwrapper.types.BuildOptions;
import net.sf.eclipsefp.haskell.buildwrapper.types.CabalImplDetails;
import net.sf.eclipsefp.haskell.buildwrapper.types.CabalMessages;
import net.sf.eclipsefp.haskell.buildwrapper.types.CabalPackage;
import net.sf.eclipsefp.haskell.buildwrapper.types.Component;
import net.sf.eclipsefp.haskell.buildwrapper.types.Component.ComponentType;
import net.sf.eclipsefp.haskell.buildwrapper.types.EvalResult;
import net.sf.eclipsefp.haskell.buildwrapper.types.ImportClean;
import net.sf.eclipsefp.haskell.buildwrapper.types.Location;
import net.sf.eclipsefp.haskell.buildwrapper.types.NameDef;
import net.sf.eclipsefp.haskell.buildwrapper.types.Note;
import net.sf.eclipsefp.haskell.buildwrapper.types.Note.Kind;
import net.sf.eclipsefp.haskell.buildwrapper.types.Occurrence;
import net.sf.eclipsefp.haskell.buildwrapper.types.OutlineDef;
import net.sf.eclipsefp.haskell.buildwrapper.types.OutlineResult;
import net.sf.eclipsefp.haskell.buildwrapper.types.ThingAtPoint;
import net.sf.eclipsefp.haskell.buildwrapper.types.TokenDef;
import net.sf.eclipsefp.haskell.buildwrapper.usage.UsageAPI;
import net.sf.eclipsefp.haskell.buildwrapper.util.BWText;
import net.sf.eclipsefp.haskell.util.FileUtil;
import net.sf.eclipsefp.haskell.util.LangUtil;
import net.sf.eclipsefp.haskell.util.OutputWriter;
import net.sf.eclipsefp.haskell.util.PlatformUtil;
import net.sf.eclipsefp.haskell.util.SingleJobQueue;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.text.IDocument;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* API facade to buildwrapper: exposes all operations and calls the build wrapper executable
* @author JP Moresmau
*
*/
public class BWFacade {
public static final String DIST_FOLDER=".dist-buildwrapper";
public static final String DIST_FOLDER_CABAL=DIST_FOLDER+"/dist";
public static final String DIST_FOLDER_CABALDEV=DIST_FOLDER+"/cabal-dev";
public static final String DIST_FOLDER_SANDBOX=DIST_FOLDER+"/sandbox";
private static final String prefix="build-wrapper-json:";
private static boolean showedNoExeError=false;
private static AtomicLong poll=new AtomicLong(0);
private int configureFailures=0;
private String bwPath;
//private String tempFolder=".dist-buildwrapper";
private CabalImplDetails cabalImplDetails;
private String cabalFile;
private String cabalShortName;
private String flags;
private List<String> extraOpts=new LinkedList<>();
private File workingDir;
private Writer outStream;
private IProject project;
private OutputWriter ow;
private List<Component> components;
private Map<String, CabalPackage[]> packageDB;
private boolean hasCabalChanged = false;
private Long lastBuild = null;
private Set<IProject> addSourceProjects = new HashSet<>();
/**
* use a long running buildwrapper process?
*/
public static boolean longRunning=true;
/**
* log build times
*/
public static final boolean logBuildTimes=false;
private BWProcessManager processManager=new BWProcessManager();
/**
* the progress monitor for the current thread, so we don't need to pass it everywhere
*/
private static ThreadLocal<IProgressMonitor> monitor=new ThreadLocal<>();
/**
* where ever we come from, we only launch one build operation at a time, and lose the intermediate operations
*/
private SingleJobQueue buildJobQueue=new SingleJobQueue();
/**
* where ever we come from, we only launch one synchronize operation at a time, and lose the intermediate operations
*/
private SingleJobQueue synchronizeJobQueue=new SingleJobQueue();
/**
* where ever we come from, we only launch one synchronize operation per file at a time, and lose the intermediate operations
*/
private Map<IFile,SingleJobQueue> syncEditorJobQueue=new HashMap<>();
/**
* query for thing at point for a given file, so that we never have more than two jobs at one time
*/
private Map<IFile,SingleJobQueue> tapQueuesByFiles=new HashMap<>();
/**
* map of outlines for files (key is relative path)
*/
private Map<String,OutlineResult> outlines=new HashMap<>();
/**
* map of flag info for files
*/
//private Map<IFile, BuildFlagInfo> flagInfos=new HashMap<IFile, BuildFlagInfo>();
private Map<IFile,String> moduleCache=Collections.synchronizedMap(new HashMap<IFile,String>());
/**
* do we need to set derived on dist dir?
*/
private boolean needSetDerivedOnDistDir=false;
private boolean hasCabalProblems=false;
public SingleJobQueue getBuildJobQueue() {
return buildJobQueue;
}
/**
* @return the synchronizeJobQueue
*/
public SingleJobQueue getSynchronizeJobQueue() {
return synchronizeJobQueue;
}
public synchronized SingleJobQueue getThingAtPointJobQueue(IFile f){
SingleJobQueue sjq=tapQueuesByFiles.get(f);
if (sjq==null){
sjq=new SingleJobQueue();
tapQueuesByFiles.put(f, sjq);
}
return sjq;
}
public synchronized Collection<SingleJobQueue> getThingAtPointJobQueues(){
return tapQueuesByFiles.values();
}
/**
* do we have a editor synchronize queue (ie have we synchronized with the editor during that session already)
* @param f
* @return
*/
public synchronized boolean hasEditorSynchronizeQueue(IFile f){
return syncEditorJobQueue.containsKey(f);
}
public synchronized SingleJobQueue getEditorSynchronizeQueue(IFile f){
SingleJobQueue sjq=syncEditorJobQueue.get(f);
if (sjq==null){
sjq=new SingleJobQueue();
syncEditorJobQueue.put(f,sjq);
}
return sjq;
}
public synchronized Collection<SingleJobQueue> getEditorSynchronizeJobQueues(){
return syncEditorJobQueue.values();
}
private void deleteCabalProblems(){
String relCabal=getCabalFile().substring(getProject().getLocation().toOSString().length());
IFile f=getProject().getFile(relCabal);
if (f!=null && f.exists()){
BuildWrapperPlugin.deleteProblems(f);
}
}
public JSONArray build(BuildOptions buildOptions){
JSONArray arrC=null;
if (buildOptions.isConfigure()){
arrC=configure(buildOptions);
if (!isOK(arrC)){
return arrC;
}
}
if (hasCabalProblems){
deleteCabalProblems();
hasCabalProblems=false;
}
LinkedList<String> command=new LinkedList<>();
command.add("build");
command.add("--output="+buildOptions.isOutput());
command.add("--cabaltarget="+buildOptions.getTarget().toString());
lastBuild=System.currentTimeMillis();
JSONArray arr=run(command,ARRAY,false);
refreshDist(true);
if (arr!=null && arrC!=null){
for (int a=0;a<arrC.length();a++){
try {
arr.put(a, arrC.get(a));
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_note_error, je);
}
}
}
return arr;
}
/**
* parse the build result JSON structure, adding markers on impacted files
* @param arr the build result as a JSON array
* @return was the build successful?
*/
public boolean parseBuildResult(JSONArray arr){
if (arr!=null && arr.length()>1){
Set<IResource> ress=new HashSet<>();
JSONObject obj=arr.optJSONObject(0);
if (obj!=null){
JSONArray files=obj.optJSONArray("fps");
if (files!=null){
for (int a=0;a<files.length();a++){
String s=files.optString(a);
if (s!=null && s.length()>0){
final IResource res=findMember(project,s);
if (res!=null){
ress.add(res);
BuildWrapperPlugin.deleteProblems(res);
}
}
}
}
}
JSONArray notes=arr.optJSONArray(1);
return parseNotes(notes,ress,null,null);
}
return true;
}
/**
* find a resource in a project or a referencing project
* @param p the project
* @param s the resource path
* @return the resource found or null if not found
*/
private static IResource findMember(IProject p,String s){
IResource res=p.findMember(s);
// linker errors may have full path
if (res==null){
String f=s.replace("\\\\", "\\");
String pos=p.getLocation().toOSString();
if (f.startsWith(pos)){
res=p.findMember(f.substring(pos.length()));
}
}
if (res==null){
for (IProject pR:p.getReferencingProjects()){
res=findMember(pR, s);
if (res!=null){
return res;
}
}
}
return res;
}
// private static String escapeFlags(String flag){
// // not needed any more: we have encoded them in Base 64
// //flag=flag.replace("\"", "\\\"");
// return flag;
// }
/**
* Get the editor stanza
* @param file
* @return
*/
public String getEditorStanza(IFile file){
try {
String s= file.getPersistentProperty(BuildWrapperPlugin.EDITORSTANZA_PROPERTY);
if (s!=null){
return s;
}
} catch (CoreException ce){
BuildWrapperPlugin.logError(ce.getLocalizedMessage(), ce);
}
BuildFlags bf=getBuildFlags(file, false);
if(bf!=null){
String s=bf.getComponent();
if (s!=null){
try {
file.setPersistentProperty(BuildWrapperPlugin.EDITORSTANZA_PROPERTY,s);
} catch (CoreException ce){
BuildWrapperPlugin.logError(ce.getLocalizedMessage(), ce);
}
return s;
}
}
return null;
}
/**
* add editor stanza name to command
* @param file
* @param command
*/
private void addEditorStanza(IFile file,List<String> command){
String editor=getEditorStanza(file);
if (editor!=null){
command.add("--component="+editor);
}
}
/**
* build one file
* @param file the file to build the temp contents
* @return the names in scope or null if the build failed
*/
public Collection<NameDef> build1(IFile file,IDocument d){
//BuildFlagInfo i=getBuildFlags(file);
String path=file.getProjectRelativePath().toOSString();
LinkedList<String> command=new LinkedList<>();
command.add("build1");
command.add("--file="+path);
addEditorStanza(file,command);
long t0=System.currentTimeMillis();
//command.add("--buildflags="+escapeFlags(i.getFlags()));
JSONArray arr=run(command,ARRAY);
if (logBuildTimes){
long t1=System.currentTimeMillis();
BuildWrapperPlugin.logInfo("build:"+(t1-t0)+"ms");
}
if (arr!=null && arr.length()>1){
Set<IResource> ress=new HashSet<>();
ress.add(file);
BuildWrapperPlugin.deleteProblems(file);
JSONArray notes=arr.optJSONArray(1);
//notes.putAll(i.getNotes());
Map<IResource,IDocument> m=new HashMap<>();
m.put(file, d);
parseNotes(notes,ress,m,null);
JSONArray names=arr.optJSONArray(0);
if(names!=null){
Collection<NameDef> ret=new ArrayList<>();
for (int a=0;a<names.length();a++){
try {
ret.add(new NameDef(names.getJSONObject(a)));
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_note_error, je);
}
}
return ret;
}
}
return null;
}
private static byte[] contCommand;
private static byte[] tokenTypesCommand;
private static byte[] endCommand;
static {
try {
contCommand=("r"+PlatformUtil.NL).getBytes(FileUtil.UTF8);
tokenTypesCommand=("t"+PlatformUtil.NL).getBytes(FileUtil.UTF8);
endCommand=("q"+PlatformUtil.NL).getBytes(FileUtil.UTF8);
} catch (UnsupportedEncodingException uee){
}
}
private BWFileInfo getFileInfo(IFile file){
return new BWFileInfo(file, getEditorStanza(file));
}
public Collection<NameDef> build1LongRunning(IFile file,IDocument d,boolean end){
//BuildFlagInfo i=getBuildFlags(file);
// avoid "Too late for parseStaticFlags: call it before newSession"
if (hasCabalChanged){
getBuildFlags(file);
hasCabalChanged=false;
}
//BuildWrapperPlugin.logInfo("build1LongRunning");
if (bwPath==null){
if (!showedNoExeError){
BuildWrapperPlugin.logError(BWText.error_noexe, null);
showedNoExeError=true;
}
return new ArrayList<>();
}
showedNoExeError=false;
BWFileInfo bfi=getFileInfo(file);
processManager.startRunningFileWait(bfi);
JSONArray arr=null;
//BuildWrapperPlugin.logInfo("build1 longrunning start");
try {
BWProcessInfo bpi=processManager.getProcess(bfi);
Process p=bpi!=null?bpi.getProcess():null;
if (p==null){
String path=file.getProjectRelativePath().toOSString();
LinkedList<String> command=new LinkedList<>();
command.add(bwPath);
command.add("build1");
command.add("--file="+path);
command.add("--longrunning=true");
command.add("--tempfolder="+DIST_FOLDER);
command.add("--cabalpath="+cabalImplDetails.getExecutable());
command.add("--cabalfile="+cabalFile);
command.add("--cabalflags="+flags);
for (String s:extraOpts){
command.add("--cabaloption="+s);
}
for (String s:cabalImplDetails.getOptions()){
command.add("--cabaloption="+s);
}
addEditorStanza(file,command);
ProcessBuilder pb=new ProcessBuilder();
pb.directory(workingDir);
pb.redirectErrorStream(true);
pb.command(command);
addBuildWrapperPath(pb);
if (ow!=null && BuildWrapperPlugin.logAnswers) {
ow.addMessage(LangUtil.join(command, " "));
}
p=pb.start();
processManager.registerProcess(bfi,p);
} else {
if (switchProcess(bpi, bfi, false)){
p.getOutputStream().write(contCommand);
}
try {
p.getOutputStream().flush();
} catch (IOException ignore){
// process has died
processManager.removeProcessForce(bfi);
end=false; // process already dead
}
}
long t0=System.currentTimeMillis();
arr=readArrayBW(p);
// check if process has ended because of some uncaught error in buildwrapper
if (processManager.hasEnded(bfi)){
end=false;
}
if (end && processManager.removeProcess(bfi)){
p.getOutputStream().write(endCommand);
try {
p.getOutputStream().flush();
} catch (IOException ignore){
// noop: flush fails if the process closed properly because write flush
}
}
if (logBuildTimes){
long t1=System.currentTimeMillis();
BuildWrapperPlugin.logInfo("build:"+(t1-t0)+"ms");
}
} catch (IOException ioe){
BuildWrapperPlugin.logError(BWText.process_launch_error, ioe);
processManager.removeProcessForce(bfi);
} finally {
processManager.stopRunningFile(bfi);
//BuildWrapperPlugin.logInfo("build1 longrunning end");
}
if (arr!=null && arr.length()>1){
Set<IResource> ress=new HashSet<>();
ress.add(file);
BuildWrapperPlugin.deleteProblems(file);
JSONArray notes=arr.optJSONArray(1);
//notes.putAll(i.getNotes());
Map<IResource,IDocument> m=new HashMap<>();
m.put(file, d);
parseNotes(notes,ress,m,null);
JSONArray names=arr.optJSONArray(0);
if(names!=null){
Collection<NameDef> ret=new ArrayList<>();
for (int a=0;a<names.length();a++){
try {
ret.add(new NameDef(names.getJSONObject(a)));
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_note_error, je);
}
}
return ret;
}
}
return null;
}
private boolean switchProcess(BWProcessInfo bpi,BWFileInfo bfi,boolean readResults) throws IOException{
if (bfi.getFile().equals(bpi.getCurrentFile())){
return true;
}
bpi.setCurrentFile(bfi.getFile());
bpi.getFiles().add(bfi.getFile());
String path=bfi.getFile().getProjectRelativePath().toOSString();
String m=getModuleName(bfi);
String expression="(\""+path+"\",\""+m+"\")";
String command="c"+expression;
Process p=bpi.getProcess();
p.getOutputStream().write((command+PlatformUtil.NL).getBytes(FileUtil.UTF8));
p.getOutputStream().flush();
if (readResults){
parseBuildResult(readArrayBW(p));
}
return false;
}
private String getModuleName(BWFileInfo bfi){
String m=moduleCache.get(bfi.getFile());
if (m==null){
m="";
BuildFlags bf=getBuildFlags(bfi.getFile());
if (bf!=null){
m= bf.getModule();
}
moduleCache.put(bfi.getFile(), m);
}
return m;
}
private JSONArray readArrayBW(Process p) throws IOException{
processManager.registerProcess(p);
try {
JSONArray arr=null;
BufferedReader br=new BufferedReader(new InputStreamReader(p.getInputStream(),FileUtil.UTF8));
//long t0=System.currentTimeMillis();
String l=br.readLine();
boolean goOn=true;
while (goOn && l!=null){
if (l.startsWith(prefix)){
if (ow!=null && BuildWrapperPlugin.logAnswers) {
ow.addMessage(l);
}
String jsons=l.substring(prefix.length()).trim();
try {
arr=ARRAY.fromJSON(jsons);
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_error, je);
}
goOn=false;
} else {
if (ow!=null) {
ow.addMessage(l);
}
l=br.readLine();
}
}
return arr;
} finally {
processManager.unregisterProcess();
}
}
/**
* end long running build process if present
* @param file
*/
public void endLongRunning(IFile file){
BWFileInfo bfi=getFileInfo(file);
BWProcessInfo bpi=processManager.getProcess(bfi);
if (bpi!=null && processManager.removeProcess(bfi)){
processManager.startRunningFileWait(bfi);
try {
endProcess(bpi.getProcess());
} finally {
processManager.stopRunningFile(bfi);
}
}
}
public static void endProcess(Process p){
try {
p.getOutputStream().write(endCommand);
try {
p.getOutputStream().flush();
} catch (IOException ignore) {
// noop: flush fails if the process already exited due to write
}
try {
// wait for exit to prevent subsequent file locking issues
p.waitFor();
} catch (InterruptedException ignore){
}
} catch (IOException ioe){
BuildWrapperPlugin.logError(BWText.process_launch_error, ioe);
}
}
// public BuildFlagInfo getBuildFlags(IFile file){
// BuildFlagInfo i=flagInfos.get(file);
// if (i==null){
// String path=file.getProjectRelativePath().toOSString();
// LinkedList<String> command=new LinkedList<String>();
// command.add("getbuildflags");
// command.add("--file="+path);
// JSONArray arr=run(command,ARRAY);
// String s="";
// JSONArray notes=new JSONArray();
// if (arr!=null && arr.length()>1){
// Set<IResource> ress=new HashSet<IResource>();
// ress.add(file);
// s=arr.optString(0);
// notes=arr.optJSONArray(1);
// }
// i=new BuildFlagInfo(s, notes);
// flagInfos.put(file, i);
// }
// return i;
// }
public JSONArray configure(BuildOptions buildOptions){
parseFlags(); // reset flags in case they have changed
//BuildWrapperPlugin.deleteProblems(getProject());
LinkedList<String> command=new LinkedList<>();
command.add("configure");
command.add("--cabaltarget="+buildOptions.getTarget().toString());
JSONArray arr=run(command,ARRAY);
if (arr!=null && arr.length()>1){
JSONArray notes=arr.optJSONArray(1);
return notes;
}
return null;
}
/**
* synchronize the project
* @param force overwrite files even if source files are not newer
*/
public void synchronize(boolean force){
// if sandbox is missing, cabal operations will fail!
if (SandboxHelper.isSandboxed(this) && (!SandboxHelper.sandboxExists(this) || !SandboxHelper.referencesSandbox(this))){
SandboxHelper.install(this);
}
LinkedList<String> command=new LinkedList<>();
command.add("synchronize");
command.add("--force="+force);
JSONArray arr=run(command,ARRAY);
boolean ok=true;
if (arr!=null){
if(arr.length()>1){
JSONArray notes=arr.optJSONArray(1);
ok=parseNotes(notes);
}
JSONArray allPaths=arr.optJSONArray(0);
if (allPaths!=null){
JSONArray paths=allPaths.optJSONArray(0);
if (paths!=null){
for (int a=0;a<paths.length();a++){
try {
String p=paths.getString(a);
if (p!=null && p.equals(cabalShortName)){
cabalFileChanged();
}
// remove from cache if file has changed
outlines.remove(p);
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_component_error, je);
}
}
}
JSONArray dels=allPaths.optJSONArray(1);
if (dels!=null && BuildWrapperPlugin.getDefault()!=null){
UsageAPI api=BuildWrapperPlugin.getDefault().getUsageAPI();
if (api!=null){
for (int a=0;a<dels.length();a++){
try {
String p=dels.getString(a);
api.removeFile(getProject(), p);
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_component_error, je);
}
}
}
}
}
}
if (ok){
if (SandboxHelper.isSandboxed(this) && (!SandboxHelper.sandboxExists(this) || !SandboxHelper.referencesSandbox(this))){
try {
SandboxHelper.installDeps(this);
} catch (CoreException ce){
BuildWrapperPlugin.logError(BWText.error_sandbox,ce);
}
}
}
}
private void refreshDist(boolean async){
Runnable r=new Runnable(){
@Override
public void run() {
IFolder fldr=getProject().getFolder(BWFacade.DIST_FOLDER);
if (fldr!=null && fldr.exists()){
try {
fldr.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
} catch (CoreException ce){
BuildWrapperPlugin.logError(BWText.error_refreshLocal, ce);
}
}
}
};
if (async){
new Thread(r).start();
} else {
r.run();
}
}
public void generateUsage(Component c,boolean returnAll){
LinkedList<String> command=new LinkedList<>();
command.add("generateusage");
command.add("--cabalcomponent="+serializeComponent(c));
command.add("--returnall="+returnAll);
JSONArray arr=run(command,ARRAY);
if (arr!=null){
// usage is a background process, we don't want to generate markers for it
/**if(arr.length()>1){
JSONArray notes=arr.optJSONArray(1);
parseNotes(notes);
}**/
JSONArray allPaths=arr.optJSONArray(0);
if (allPaths!=null){
if (allPaths.length()>0){
refreshDist(false);
}
BuildWrapperPlugin plugin=BuildWrapperPlugin.getDefault();
if (plugin!=null){
for (int a=0;a<allPaths.length();a++){
try {
String p=allPaths.getString(a);
UsageAPI api=plugin.getUsageAPI();
if (api!=null){
api.addFile(getProject(),c, p);
}
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.error_parsing_usage_path, je);
}
}
}
}
}
}
public void cabalFileChanged(){
components=null;
packageDB=null;
hasCabalChanged=true;
//flagInfos.clear();
}
public boolean synchronize1(IFile file,boolean force){
String path=file.getProjectRelativePath().toOSString();
LinkedList<String> command=new LinkedList<>();
command.add("synchronize1");
command.add("--file="+path);
command.add("--force="+force);
String s=run(command,STRING);
return s!=null;
}
public File write(IFile file,String contents){
/*String path=file.getProjectRelativePath().toOSString();
LinkedList<String> command=new LinkedList<String>();
command.add("write");
command.add("--file="+path);
//command.add("--contents="+contents);
//command.add("--contents=\""+contents.replace("\"", "\\\"")+"\"");
command.add("--contents="+contents.replace("\"", "\\\""));
String s=run(command,STRING);
return s!=null;*/
try {
String path=file.getProjectRelativePath().toOSString();
File tgt=new File(new File(workingDir,DIST_FOLDER),path);
tgt.getParentFile().mkdirs();
write(file,tgt, contents);
return tgt;
} catch (Exception e){
BuildWrapperPlugin.logError(e.getLocalizedMessage(), e);
}
return null;
}
public boolean write(IFile file,File tgt,String contents){
outlines.remove(file.getProjectRelativePath().toOSString());
try {
FileUtil.writeSharedFile(tgt, contents, 5);
} catch (Exception e){
BuildWrapperPlugin.logError(e.getLocalizedMessage(), e);
}
return false;
}
public List<Component> getComponents(){
if (components!=null && components.size()>0){
return components;
}
LinkedList<String> command=new LinkedList<>();
command.add("components");
JSONArray arr=run(command,ARRAY);
List<Component> cps=new LinkedList<>();
if (arr!=null){
if (arr.length()>1){
JSONArray notes=arr.optJSONArray(1);
parseNotes(notes);
}
JSONArray objs=arr.optJSONArray(0);
for (int a=0;a<objs.length();a++){
try {
cps.add(parseComponent(objs.getJSONObject(a)));
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_component_error, je);
}
}
}
components=cps;
return components;
}
public Map<String, CabalPackage[]> getPackagesByDB() {
if (packageDB!=null){
return packageDB;
}
LinkedList<String> command=new LinkedList<>();
command.add("dependencies");
if (SandboxHelper.isSandboxed(this)){
command.add("--sandbox="+getCabalImplDetails().getSandboxPath());
}
JSONArray arr=run(command,ARRAY);
if (arr==null){
return new HashMap<>();
}
Map<String, CabalPackage[]> cps=new HashMap<>();
if (arr.length()>1){
JSONArray notes=arr.optJSONArray(1);
parseNotes(notes);
}
JSONArray objs=arr.optJSONArray(0);
for (int a=0;a<objs.length();a++){
try {
JSONArray arr1=objs.getJSONArray(a);
String fp=arr1.getString(0);
JSONArray arr2=arr1.getJSONArray(1);
CabalPackage[] pkgs=new CabalPackage[arr2.length()];
for (int b=0;b<arr2.length();b++){
pkgs[b]=parsePackage(arr2.getJSONObject(b));
}
cps.put(fp, pkgs);
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_package_error, je);
}
}
packageDB=cps;
return packageDB;
}
public OutlineResult outline(IFile file,IDocument d){
String path=file.getProjectRelativePath().toOSString();
OutlineResult or=outlines.get(path);
if (or!=null){
return or;
}
//BuildFlagInfo i=getBuildFlags(file);
LinkedList<String> command=new LinkedList<>();
command.add("outline");
command.add("--file="+path);
addEditorStanza(file,command);
//command.add("--buildflags="+escapeFlags(i.getFlags()));
JSONArray arr=run(command,ARRAY);
or=new OutlineResult();
if (arr!=null){
JSONObject obj=arr.optJSONObject(0);
if (obj!=null){
try {
or=new OutlineResult(file, obj);
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_outline_error, je);
}
} else {
// old version pre 0.2.3
JSONArray objs=arr.optJSONArray(0);
for (int a=0;a<objs.length();a++){
try {
or.getOutlineDefs().add(new OutlineDef(file,objs.getJSONObject(a)));
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_outline_error, je);
}
}
}
if (arr.length()>1){
JSONArray notes=arr.optJSONArray(1);
//notes.putAll(i.getNotes());
List<Note> ns=new ArrayList<>();
Map<IResource,IDocument> m=new HashMap<>();
m.put(file, d);
boolean b=parseNotes(notes,null,m,ns);
or.setNotes(ns);
or.setBuildOK(b);
}
}
registerOutline(file,or);
return or;
}
public void registerOutline(IFile file,OutlineResult or){
String path=file.getProjectRelativePath().toOSString();
outlines.put(path,or);
}
public List<TokenDef> tokenTypes(IFile file){
//long t0=System.currentTimeMillis();
JSONArray arr=null;
BWFileInfo bfi=getFileInfo(file);
if (processManager.startRunningFile(bfi)){
BWProcessInfo bpi=processManager.getProcess(bfi);
try {
if (bpi!=null){
switchProcess(bpi, bfi, true);
//BuildWrapperPlugin.logInfo("tokenTypes longrunning start");
bpi.getProcess().getOutputStream().write(tokenTypesCommand);
bpi.getProcess().getOutputStream().flush();
arr=readArrayBW(bpi.getProcess());
}
//BuildWrapperPlugin.logInfo("tokenTypes longrunning");
} catch (IOException ioe){
//BuildWrapperPlugin.logError(BWText.process_launch_error, ioe);
} finally {
//BuildWrapperPlugin.logInfo("tokenTypes longrunning end");
processManager.stopRunningFile(bfi);
}
}
if (arr==null){
String path=file.getProjectRelativePath().toOSString();
LinkedList<String> command=new LinkedList<>();
command.add("tokentypes");
command.add("--file="+path);
addEditorStanza(file,command);
arr=run(command,ARRAY);
}
//long t01=System.currentTimeMillis();
List<TokenDef> cps;
if (arr!=null){
if (arr.length()>1){
JSONArray notes=arr.optJSONArray(1);
parseNotes(notes);
}
JSONArray objs=arr.optJSONArray(0);
cps=new ArrayList<>(objs.length());
String fn=file.getLocation().toOSString();
for (int a=0;a<objs.length();a++){
try {
cps.add(new TokenDef(fn,objs.getJSONObject(a)));
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_outline_error, je);
}
}
} else {
cps=new ArrayList<>();
}
//long t1=System.currentTimeMillis();
//BuildWrapperPlugin.logInfo("tokenTypes:"+(t1-t0)+"ms, parsing:"+(t1-t01)+"ms");
return cps;
}
public List<TokenDef> tokenTypes(String fn){
//long t0=System.currentTimeMillis();
LinkedList<String> command=new LinkedList<>();
command.add("tokentypes");
command.add("--file="+fn);
JSONArray arr=run(command,ARRAY);
//long t01=System.currentTimeMillis();
List<TokenDef> cps;
if (arr!=null){
if (arr.length()>1){
JSONArray notes=arr.optJSONArray(1);
parseNotes(notes);
}
JSONArray objs=arr.optJSONArray(0);
cps=new ArrayList<>(objs.length());
for (int a=0;a<objs.length();a++){
try {
cps.add(new TokenDef(fn,objs.getJSONObject(a)));
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_outline_error, je);
}
}
} else {
cps=new ArrayList<>();
}
//long t1=System.currentTimeMillis();
//BuildWrapperPlugin.logInfo("tokenTypes:"+(t1-t0)+"ms, parsing:"+(t1-t01)+"ms");
return cps;
}
public BuildFlags getBuildFlags(IFile file){
return getBuildFlags(file,true);
}
public BuildFlags getBuildFlags(IFile file,boolean withStanza){
String path=file.getProjectRelativePath().toOSString();
LinkedList<String> command=new LinkedList<>();
command.add("getbuildflags");
command.add("--file="+path);
if (withStanza){
addEditorStanza(file,command);
}
JSONArray arr=run(command,ARRAY);
if (arr!=null){
if (arr.length()>1){
JSONArray notes=arr.optJSONArray(1);
//notes.putAll(i.getNotes());
parseNotes(notes);
}
JSONObject obj=arr.optJSONObject(0);
if (obj!=null){
return new BuildFlags(obj);
}
}
return null;
}
public List<ImportClean> cleanImports(IFile file,boolean format){
String path=file.getProjectRelativePath().toOSString();
LinkedList<String> command=new LinkedList<>();
command.add("cleanimports");
command.add("--file="+path);
command.add("--format="+format);
addEditorStanza(file,command);
JSONArray arr=run(command,ARRAY);
List<ImportClean> ret=new ArrayList<>();
if (arr!=null){
if (arr.length()>1){
JSONArray notes=arr.optJSONArray(1);
//notes.putAll(i.getNotes());
parseNotes(notes);
}
JSONArray locs=arr.optJSONArray(0);
if (locs!=null){
for (int a=0;a<locs.length();a++){
try {
JSONObject o=locs.getJSONObject(a);
ret.add(new ImportClean(file,o));
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_import_clean_error, je);
}
}
}
}
return ret;
}
public List<Occurrence> getOccurrences(IFile file,String s){
//BuildFlagInfo i=getBuildFlags(file);
long t0=System.currentTimeMillis();
String path=file.getProjectRelativePath().toOSString();
LinkedList<String> command=new LinkedList<>();
command.add("occurrences");
command.add("--file="+path);
command.add("--token="+s);
addEditorStanza(file,command);
//command.add("--buildflags="+escapeFlags(i.getFlags()));
JSONArray arr=run(command,ARRAY);
List<Occurrence> cps;
if (arr!=null){
if (arr.length()>1){
JSONArray notes=arr.optJSONArray(1);
//notes.putAll(i.getNotes());
parseNotes(notes);
}
JSONArray objs=arr.optJSONArray(0);
cps=new ArrayList<>(objs.length());
String fn=file.getLocation().toOSString();
for (int a=0;a<objs.length();a++){
try {
TokenDef td=new TokenDef(fn,objs.getJSONObject(a));
cps.add(new Occurrence(td));
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_outline_error, je);
}
}
} else {
cps=new ArrayList<>();
}
if (logBuildTimes){
long t1=System.currentTimeMillis();
BuildWrapperPlugin.logInfo("occurrences:"+(t1-t0)+"ms");
}
return cps;
}
public ThingAtPoint getThingAtPoint(IFile file,Location location){
//BuildFlagInfo i=getBuildFlags(file);
//long t0=System.currentTimeMillis();
BWFileInfo bfi=getFileInfo(file);
JSONArray arr=null;
if (processManager.startRunningFile(bfi)){
try {
BWProcessInfo bpi=processManager.getProcess(bfi);
if (bpi!=null){
switchProcess(bpi, bfi, true);
Process p=bpi.getProcess();
//BuildWrapperPlugin.logInfo("getThingAtPoint longrunning start");
String command="p("+location.getStartLine()+","+(location.getStartColumn()+1)+")";
p.getOutputStream().write((command+PlatformUtil.NL).getBytes(FileUtil.UTF8));
p.getOutputStream().flush();
arr=readArrayBW(p);
}
} catch (IOException ioe){
BuildWrapperPlugin.logError(BWText.process_launch_error, ioe);
} finally {
//BuildWrapperPlugin.logInfo("getThingAtPoint longrunning end");
processManager.stopRunningFile(bfi);
}
}
if (arr==null){
String path=file.getProjectRelativePath().toOSString();
LinkedList<String> command=new LinkedList<>();
command.add("thingatpoint");
command.add("--file="+path);
command.add("--line="+location.getStartLine());
command.add("--column="+(location.getStartColumn()+1));
addEditorStanza(file,command);
//command.add("--buildflags="+escapeFlags(i.getFlags()));
arr=run(command,ARRAY);
}
ThingAtPoint tap=null;
if (arr!=null){
if (arr.length()>1){
JSONArray notes=arr.optJSONArray(1);
// notes.putAll(i.getNotes());
parseNotes(notes);
}
JSONObject o=arr.optJSONObject(0);
if (o!=null){
try {
tap=new ThingAtPoint(o);
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_thingatpoint_error, je);
}
}
}
//long t1=System.currentTimeMillis();
//BuildWrapperPlugin.logInfo("getThingAtPoint:"+(t1-t0)+"ms");
return tap;
}
/**
* get locals from location
* @param file
* @param location
* @return
*/
public List<ThingAtPoint> getLocals(IFile file,Location location){
//long t0=System.currentTimeMillis();
JSONArray arr=null;
BWFileInfo bfi=getFileInfo(file);
//boolean run=false;
if (processManager.startRunningFile(bfi)){
try {
BWProcessInfo bpi=processManager.getProcess(bfi);
if (bpi!=null){
switchProcess(bpi, bfi, true);
Process p=bpi.getProcess();
//BuildWrapperPlugin.logInfo("getThingAtPoint longrunning start");
String command="l("+location.getStartLine()+","+(location.getStartColumn()+1)+","+location.getEndLine()+","+(location.getEndColumn()+1)+")";
p.getOutputStream().write((command+PlatformUtil.NL).getBytes(FileUtil.UTF8));
p.getOutputStream().flush();
arr=readArrayBW(p);
//run=true;
}
} catch (IOException ioe){
BuildWrapperPlugin.logError(BWText.process_launch_error, ioe);
} finally {
//BuildWrapperPlugin.logInfo("getThingAtPoint longrunning end");
processManager.stopRunningFile(bfi);
}
}
if (arr==null){
String path=file.getProjectRelativePath().toOSString();
LinkedList<String> command=new LinkedList<>();
command.add("locals");
command.add("--file="+path);
command.add("--sline="+location.getStartLine());
command.add("--scolumn="+(location.getStartColumn()+1));
command.add("--eline="+location.getEndLine());
command.add("--ecolumn="+(location.getEndColumn()+1));
addEditorStanza(file,command);
arr=run(command,ARRAY);
}
List<ThingAtPoint> taps=new ArrayList<>();
if (arr!=null){
if (arr.length()>1){
JSONArray notes=arr.optJSONArray(1);
// notes.putAll(i.getNotes());
parseNotes(notes);
}
JSONArray locs=arr.optJSONArray(0);
if (locs!=null){
for (int a=0;a<locs.length();a++){
try {
JSONObject o=locs.getJSONObject(a);
taps.add(new ThingAtPoint(o));
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_thingatpoint_error, je);
}
}
}
}
//long t1=System.currentTimeMillis();
//BuildWrapperPlugin.logInfo("getLocals:"+(t1-t0)+"ms ("+taps.size()+","+run+")");
return taps;
}
public void clean(boolean everything){
LinkedList<String> command=new LinkedList<>();
command.add("clean");
command.add("--everything="+everything);
run(command,BOOL);
}
private boolean parseNotes(JSONArray notes){
return parseNotes(notes,null,null,null);
}
private boolean isOK(JSONArray notes){
if (notes!=null){
try {
for (int a=0;a<notes.length();a++){
JSONObject o=notes.getJSONObject(a);
String sk=o.getString("s");
Kind k="Error".equalsIgnoreCase(sk)?Kind.ERROR:Kind.WARNING;
if (k.equals(Kind.ERROR)){
return false;
}
}
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_note_error, je);
}
}
return true;
}
/**
* parse notes from JSON, either adding them as markers or putting them into the collect
* @param notes
* @param ress
* @param m
* @param collect
* @return
*/
private boolean parseNotes(JSONArray notes,Set<IResource> ress,Map<IResource,IDocument> m,Collection<Note> collect){
boolean buildOK=true;
if (notes!=null){
try {
if (ress==null){
ress=new HashSet<>();
}
for (int a=0;a<notes.length();a++){
JSONObject o=notes.getJSONObject(a);
String sk=o.getString("s");
Kind k="Error".equalsIgnoreCase(sk)?Kind.ERROR:Kind.WARNING;
JSONObject ol=o.getJSONObject("l");
String f=ol.getString("f");
int line=ol.getInt("l");
int col=ol.getInt("c");
int endline=ol.getInt("el");
int endcol=ol.getInt("ec");
//ow.addMessage("\nParsed location: src="+f+" "+line+":"+col+" to "+endline+":"+endcol+" msg:"+o.getString("t"));
Location loc=new Location(project, f, line, col, endline, endcol);
//ow.addMessage("\nCreated location: "+loc.getStartLine()+":"+loc.getStartColumn()+" to "+loc.getEndLine()+":"+loc.getEndColumn());
if (k.equals(Kind.ERROR)){
buildOK=false;
}
Note n=new Note(k,loc,o.getString("t"),"");
if (collect!=null){
collect.add(n);
} else {
IResource res=findMember(project,f);
if (res!=null){
if (ress.add(res)){
BuildWrapperPlugin.deleteProblems(res);
}
if (res.getLocation().toOSString().equals(getCabalFile())){
if (isOtherProject(project, n.getMessage())){
continue;
}
hasCabalProblems=true;
}
try {
n.applyAsMarker(res,m!=null?m.get(res):null);
} catch (CoreException ce){
BuildWrapperPlugin.logError(BWText.process_apply_note_error, ce);
}
}
}
}
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_note_error, je);
}
}
return buildOK;
}
private static boolean isOtherProject(IProject p,String message){
boolean other=false;
for (IProject pR:p.getReferencingProjects()){
if (message.startsWith(pR.getName())){
return true;
}
other=isOtherProject(pR, message);
if (other){
return other;
}
}
return other;
}
private Component parseComponent(JSONObject obj){
boolean buildable=false;
try {
if (obj.has("Library")){
buildable=obj.getBoolean("Library");
return new Component(ComponentType.LIBRARY, null, getCabalFile(), buildable);
} else if (obj.has("Executable")){
buildable=obj.getBoolean("Executable");
return new Component(ComponentType.EXECUTABLE, obj.getString("e"), getCabalFile(), buildable);
} else if (obj.has("TestSuite")){
buildable=obj.getBoolean("TestSuite");
return new Component(ComponentType.TESTSUITE, obj.getString("t"), getCabalFile(), buildable);
} else if (obj.has("Benchmark")){
buildable=obj.getBoolean("Benchmark");
return new Component(ComponentType.BENCHMARK, obj.getString("b"), getCabalFile(), buildable);
}
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_component_error, je);
}
return null;
}
private String serializeComponent(Component c) {
return ComponentType.LIBRARY.equals(c.getType())?"":c.getName();
}
private CabalPackage parsePackage(JSONObject obj){
try {
String name=obj.getString("n");
String version=obj.getString("v");
boolean exposed=obj.getBoolean("e");
JSONArray comps=obj.getJSONArray("d");
JSONArray mods=obj.getJSONArray("m");
CabalPackage cp=new CabalPackage();
cp.setName(name);
cp.setVersion(version);
cp.setExposed(exposed);
for (int a=0;a<mods.length();a++){
cp.getModules().add(mods.getString(a));
}
Component[] deps=new Component[comps.length()];
for (int a=0;a<comps.length();a++){
Component c=parseComponent(comps.getJSONObject(a));
deps[a]=c;
}
cp.setComponents(deps);
return cp;
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_package_error, je);
}
return null;
}
/**
* flag caching if we need to add the path
*/
private Boolean needPath=null;
/**
* add the buildwrapper location to the path so that other executables can be found
* @param pb
*/
private synchronized void addBuildWrapperPath(ProcessBuilder pb){
if ((needPath==null || needPath.booleanValue()) && bwPath!=null){
needPath=false;
String path=new File(bwPath).getParent();
if (path!=null){
Map<String,String> env=pb.environment();
String pathValue=FileUtil.getPath(env);
if (Boolean.TRUE.equals(needPath) || pathValue==null || pathValue.length()==0 || !pathValue.contains(path)){
if (pathValue==null || pathValue.length()==0){
pathValue=path;
} else {
pathValue+=File.pathSeparator+path;
}
env.put(FileUtil.getPathVariable(env),pathValue);
needPath=true;
}
}
}
}
private <T> T run(LinkedList<String> args,JSONFactory<T> f){
return run(args,f,true);
}
/**
* synchronized to avoid concurrent executions of stuff
* @param args
* @param f
* @param canRerun
* @return
*/
private <T> T run(LinkedList<String> args,JSONFactory<T> f,boolean canRerun){
if (bwPath==null){
if (!showedNoExeError){
BuildWrapperPlugin.logError(BWText.error_noexe, null);
showedNoExeError=true;
}
return null;
}
if (isCanceled()){
return null;
}
showedNoExeError=false;
boolean isConfigureAction=args.size()>0 && ("synchronize".equals(args.get(0)) || "configure".equals(args.get(0)) || "build".equals(args.get(0)));
args.addFirst(bwPath);
args.add("--tempfolder="+DIST_FOLDER);
if (cabalImplDetails!=null){
args.add("--cabalpath="+cabalImplDetails.getExecutable());
}
args.add("--cabalfile="+cabalFile);
args.add("--cabalflags="+flags);
if (!args.get(1).equals("build")){
for (String s:extraOpts){
args.add("--cabaloption="+s);
}
}
if (cabalImplDetails!=null){
for (String s:cabalImplDetails.getOptions()){
args.add("--cabaloption="+s);
}
}
if (ow!=null && BuildWrapperPlugin.logAnswers) {
args.add("--logcabal=true");
}
ProcessBuilder pb=new ProcessBuilder();
pb.directory(workingDir);
pb.redirectErrorStream(true);
pb.command(args);
addBuildWrapperPath(pb);
if (isCanceled()){
return null;
}
if (ow!=null && BuildWrapperPlugin.logAnswers) {
ow.addMessage(LangUtil.join(args, " "));
}
T obj=null;
if (isCanceled()){
return obj;
}
try {
Process p=pb.start();
processManager.registerProcess(p);
BufferedReader br=new BufferedReader(new InputStreamReader(p.getInputStream(),FileUtil.UTF8));
//long t0=System.currentTimeMillis();
String l=br.readLine();
boolean goOn=true;
boolean needConfigure=false;
boolean needDelete=false;
while (goOn && l!=null){
/*if (outStream!=null){
outStream.write(l);
outStream.write(PlatformUtil.NL);
outStream.flush();
}*/
if (l.startsWith(prefix)){
if (ow!=null && BuildWrapperPlugin.logAnswers) {
ow.addMessage(l);
}
String jsons=l.substring(prefix.length()).trim();
try {
obj=f.fromJSON(jsons);
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_error, je);
}
goOn=false;
} else {
String ll=l.toLowerCase(Locale.ENGLISH);
if (ll.contains(CabalMessages.RERUN_CONFIGURE) || ll.contains(CabalMessages.CANNOT_SATISFY)){
if (ll.contains(CabalMessages.VERSION)){
needDelete=true;
}
needConfigure=true;
}
if (ow!=null) {
ow.addMessage(l);
}
}
if (goOn){
l=br.readLine();
}
}
processManager.unregisterProcess();
if (isCanceled()){
return obj;
}
if (needConfigure && canRerun && !isConfigureAction){
if (needDelete){
try {
IFolder fldr=project.getFolder(DIST_FOLDER);
if (fldr.exists()){
fldr.delete(IResource.FORCE, new NullProgressMonitor());
}
} catch (Throwable ce){
BuildWrapperPlugin.logError(BWText.process_launch_error, ce);
}
}
configure(new BuildOptions());
return run(new LinkedList<>(args.subList(1, args.size()-4)),f,false);
} else if (needDelete){
configureFailures++;
if (BuildWrapperPlugin.getMaxConfigureFailures()>=0 && configureFailures>=BuildWrapperPlugin.getMaxConfigureFailures()){
BuildWrapperPlugin.logError(BWText.error_toomanyfailures, null);
showedNoExeError=true;
bwPath=null;
}
// we reran and we don't need to configure after that call, we reset the count
} else if (!canRerun && !needConfigure){
configureFailures=0;
}
// maybe now the folder exists...
if (needSetDerivedOnDistDir){
setDerived();
}
//long t1=System.currentTimeMillis();
//BuildWrapperPlugin.logInfo("read run:"+(t1-t0)+"ms");
} catch (IOException ioe){
processManager.unregisterProcess();
if (!isCanceled()){
BuildWrapperPlugin.logError(BWText.process_launch_error, ioe);
}
}
return obj;
}
/**
* run cabal with the given argument, using the flags defined on the project
* @param args
*/
public void runCabal(LinkedList<String> args,File workDir){
runCabal(args,flags,workDir);
}
/**
* run cabal with the given argument, using the provided flags
* @param args
* @param explicitFlags
*/
public void runCabal(LinkedList<String> args,String explicitFlags,File workDir){
if (isCanceled()){
return;
}
String action=args.get(0);
args.addFirst(cabalImplDetails.getExecutable());
// we should need flags and extra options ONLY on configure calls...
if (action.equals("configure")){
if (explicitFlags!=null && explicitFlags.length()>0){
args.add("--flags="+explicitFlags);
}
for (String s:extraOpts){
args.add(s);
}
}
for (String s:cabalImplDetails.getOptions()){
// https://github.com/haskell/cabal/issues/1511
// sandbox init needs GHC but doesn't accept --with-ghc flag
// && !action.equals("sandbox")
if (s.startsWith("--with-ghc") && !action.equals("configure") && !action.equals("install") ){
continue;
}
args.add(s);
}
// no we can't build one project in the directory of another
// because
// we pass the build dir as an absolute path otherwise there's confusion, I think cabal-dev launches cabal in the project directory...
//File bd=new File (workingDir,BWFacade.DIST_FOLDER_CABAL);
//args.add("--builddir="+BWFacade.DIST_FOLDER_CABAL);
ProcessBuilder pb=new ProcessBuilder();
if (workDir==null){
pb.directory(workingDir);
} else {
workDir.mkdirs();
pb.directory(workDir);
}
pb.redirectErrorStream(true);
pb.command(args);
addBuildWrapperPath(pb);
if (ow!=null && BuildWrapperPlugin.logAnswers) {
ow.addMessage(LangUtil.join(args, " "));
}
if (isCanceled()){
return;
}
try {
Process p=pb.start();
processManager.registerProcess(p);
try {
BufferedReader br=new BufferedReader(new InputStreamReader(p.getInputStream()));
//long t0=System.currentTimeMillis();
String l=br.readLine();
while (l!=null){
if (ow!=null) {
ow.addMessage(l);
}
l=br.readLine();
}
// maybe now the folder exists...
if (needSetDerivedOnDistDir){
setDerived();
}
} finally {
processManager.unregisterProcess();
}
//long t1=System.currentTimeMillis();
//BuildWrapperPlugin.logInfo("read run:"+(t1-t0)+"ms");
} catch (IOException ioe){
if (!isCanceled()){
BuildWrapperPlugin.logError(BWText.process_launch_error, ioe);
}
}
}
public OutputWriter getOutputWriter() {
return ow;
}
// public String getTempFolder() {
// return tempFolder;
// }
//
//
// public void setTempFolder(String tempFolder) {
// this.tempFolder = tempFolder;
// }
public String getCabalFile() {
return cabalFile;
}
public void setCabalFile(String cabalFile) {
this.cabalFile = cabalFile;
if (this.cabalFile!=null){
cabalShortName=new File(this.cabalFile).getName();
}
}
public File getWorkingDir() {
return workingDir;
}
public void setWorkingDir(File workingDir) {
this.workingDir = workingDir;
}
public Writer getOutStream() {
return outStream;
}
public void setOutStream(Writer outStream) {
this.outStream = outStream;
if (ow!=null){
ow.setTerminate();
}
ow=outStream!=null?new OutputWriter("BWFacade.outputWriter: "+project.getName(),outStream) {
@Override
public void onThrowable(Throwable se) {
BuildWrapperPlugin.logError(se.getLocalizedMessage(), se);
}
@Override
public void onIOError(IOException ex) {
BuildWrapperPlugin.logError(ex.getLocalizedMessage(), ex);
}
}:null;
if (ow!=null){
ow.start();
}
}
public String getBwPath() {
return bwPath;
}
public void setBwPath(String bwPath) {
this.bwPath = bwPath;
needPath=null;
}
private static interface JSONFactory<T>{
T fromJSON(String json) throws JSONException;
}
private static JSONFactory<JSONArray> ARRAY=new JSONFactory<JSONArray>() {
@Override
public JSONArray fromJSON(String json)throws JSONException {
return new JSONArray(json);
}
};
// private static JSONFactory<JSONObject> OBJECT=new JSONFactory<JSONObject>() {
// public JSONObject fromJSON(String json)throws JSONException {
// return new JSONObject(json);
// }
// };
private static JSONFactory<String> STRING=new JSONFactory<String>() {
@Override
public String fromJSON(String json)throws JSONException {
return json;
}
};
private static JSONFactory<Boolean> BOOL=new JSONFactory<Boolean>() {
@Override
public Boolean fromJSON(String json)throws JSONException {
return Boolean.valueOf(json);
}
};
public IProject getProject() {
return project;
}
public void setProject(IProject project) {
this.project = project;
parseFlags();
setDerived();
}
/**
* wait in a thread for the runnable to finish, or the monitor to be cancelled
* @param r the runnable
* @param mon the monitor, maybe null
*/
public void waitForThread(Runnable r,final IProgressMonitor mon){
waitForThread(r,mon,0);
}
/**
* wait in a thread for the runnable to finish, or the monitor to be cancelled
* or a certain amount of seconds to elapse
* @param r the runnable
* @param mon the monitor, maybe null
* @param maxSecs the maximum running time in seconds (0 or less: no limit)
*/
public void waitForThread(Runnable r,final IProgressMonitor mon,final int maxSecs){
// no monitor, nothing to do
if (mon==null){
r.run();
} else {
// register the monitor for the current thread
final Thread jobThread=Thread.currentThread();
monitor.set(mon);
// should the polling thread continue?
final AtomicBoolean doPoll=new AtomicBoolean(true);
try {
final long t0=System.currentTimeMillis();
final long max=maxSecs>0?t0+(maxSecs*1000):Long.MAX_VALUE;
//BuildWrapperPlugin.logInfo("maxSecs:"+maxSecs+",t0:"+t0+",max:"+max);
// the runnable checking if the monitor has been canceled
Runnable check=new Runnable(){
@Override
public void run() {
//BuildWrapperPlugin.logInfo("start poll");
while (doPoll.get()){
try {
Thread.sleep(1000); // wait a second
} catch (InterruptedException ie){
}
boolean expired=System.currentTimeMillis()>max;
if (expired){
//BuildWrapperPlugin.logInfo(String.valueOf(System.currentTimeMillis()));
BuildWrapperPlugin.logWarning(BWText.process_expired, null);
}
// canceled!
if (mon.isCanceled() || expired ){
//BuildWrapperPlugin.logInfo("canceled");
// do we have a process?
Process p=processManager.unregisterProcess(jobThread);
if (p!=null){
//buildProcesses.values().remove(p);
//BuildWrapperPlugin.logInfo("destroy");
p.destroy(); // destroy the process
}
return;
}
}
//BuildWrapperPlugin.logInfo("end poll");
}
};
// start polling thread
Thread t=new Thread(check,"PollingThread-"+(poll.getAndIncrement()));
t.setDaemon(true);
t.start();
// run the runnable in the current thread, otherwise we get into deadlock (the job waits for the thread, the thread does something that requires a lock on the object tat the job is on)
r.run();
} finally {
// stop polling
doPoll.set(false);
// remove monitor
monitor.set(null);
}
}
}
/**
* clean: delete the .dist-buildwrapper folder, and synchronize the full content
* @param mon the progress monitor
* @throws CoreException
*/
public void clean(final IProgressMonitor mon) throws CoreException{
if (project!=null){
closeAllProcesses();
if (mon!=null && mon.isCanceled()){
return;
}
/**
* closes processes so they don't have locks on resources we'd like to delete
* and they can then be restarted with the newly generated files
*/
Runnable r1=new Runnable(){
@Override
public void run() {
clean(true);
}
};
waitForThread(r1, mon);
project.refreshLocal(IResource.DEPTH_ONE, mon);
setDerived();
if (mon!=null && mon.isCanceled()){
return;
}
deleteCabalProblems();
BuildWrapperPlugin.deleteAllProblems(project);
cabalFileChanged();
if (mon!=null && mon.isCanceled()){
return;
}
outlines.clear();
// why does clean recalculate dependencies?
// if the sandbox is inside the project...
if (getCabalImplDetails().isSandboxed() && !getCabalImplDetails().isUniqueSandbox()){
Runnable r2=new Runnable(){
@Override
public void run() {
try {
SandboxHelper.installDeps(BWFacade.this);
} catch (CoreException ce){
BuildWrapperPlugin.logError(BWText.error_sandbox,ce);
}
}
};
waitForThread(r2, mon);
}
Runnable r3=new Runnable(){
@Override
public void run() {
synchronize(false);
}
};
waitForThread(r3, mon);
}
}
/**
* close all long running processes
*/
public void closeAllProcesses(){
processManager.closeAll();
}
public void cleanGenerated(){
clean(false);
}
/**
* set dist folder as derived so that it will be ignored in searches, etc.
*/
private void setDerived(){
if (project!=null){
IFolder fldr=project.getFolder(DIST_FOLDER);
if (fldr.exists()){
if (!fldr.isDerived()){
if (!project.getWorkspace().isTreeLocked()){
try {
fldr.setDerived(true,new NullProgressMonitor());
needSetDerivedOnDistDir=false; // ok
} catch (CoreException ce){
// log error and leave flag to false, let's hope it'll be better at next run
BuildWrapperPlugin.logError(BWText.error_derived, ce);
needSetDerivedOnDistDir=false; // let's not cause errors again
}
} else {
needSetDerivedOnDistDir=true; // tree is locked, le's try again when it's not
}
} else {
needSetDerivedOnDistDir=false; // folder is already marked
}
} else {
needSetDerivedOnDistDir=true; // folder doesn't exist, let's try again when it does
}
}
}
/**
* Return the flags parameter to cabal configure
* @return the flags
*/
public String getFlags() {
return flags;
}
/**
* Return the extra options passed to cabal configure
* @return the extraOpts
*/
public List<String> getExtraOpts() {
return extraOpts;
}
private void parseFlags(){
try {
String currentProp=project.getPersistentProperty( BuildWrapperPlugin.USERFLAGS_PROPERTY );
JSONObject flagO=new JSONObject();
if (currentProp!=null && currentProp.length()>0){
flagO=new JSONObject( currentProp );
}
StringBuilder sb=new StringBuilder();
String sep="";
for (Iterator<String> it=flagO.keys();it.hasNext();){
String s=it.next();
boolean b=flagO.getBoolean(s);
sb.append(sep);
sep=" ";
if (!b){
sb.append("-");
}
sb.append(s);
}
flags=sb.toString();
extraOpts.clear();
currentProp=project.getPersistentProperty( BuildWrapperPlugin.EXTRAOPTS_PROPERTY );
JSONArray arrOpts=new JSONArray();
if (currentProp!=null && currentProp.length()>0){
arrOpts=new JSONArray( currentProp );
}
for (int a=0;a<arrOpts.length();a++){
String s=arrOpts.getString(a);
if (s!=null && s.length()>0){
extraOpts.add(s);
}
}
} catch (Exception e){
BuildWrapperPlugin.logError(BWText.error_gettingFlags, e);
}
}
public boolean isInTempFolder(IResource r){
if (r.getProject().equals(getProject()) && getProject().getFolder(DIST_FOLDER).getFullPath().isPrefixOf(r.getFullPath())){
return true;
}
return false;
}
public CabalImplDetails getCabalImplDetails() {
return cabalImplDetails;
}
public void setCabalImplDetails(CabalImplDetails cabalImplDetails) {
if (this.cabalImplDetails!=null && cabalImplDetails!=null){
if (this.cabalImplDetails.getSandboxPath()!=null && cabalImplDetails.getSandboxPath()!=null){
if (!this.cabalImplDetails.getSandboxPath().equals(cabalImplDetails.getSandboxPath())){
SandboxHelper.sandboxLocationChanged(this);
}
}
}
this.cabalImplDetails = cabalImplDetails;
this.addSourceProjects.clear();
}
public boolean isCanceled(){
IProgressMonitor mon=monitor.get();
if (mon!=null && mon.isCanceled()){
return true;
}
return false;
}
public File getSandboxPath(){
if (getCabalImplDetails().isSandboxed()){
if (getCabalImplDetails().isUniqueSandbox()){
return new File (getCabalImplDetails().getSandboxPath());
} else {
return new File(getProject().getLocation().toOSString(),getCabalImplDetails().getSandboxPath());
}
}
return null;
}
/**
* evaluate an expression
* @param file
* @param expression
* @return a (possibly multiple) list of results
*/
public List<EvalResult> eval(IFile file,String expression){
// avoid "Too late for parseStaticFlags: call it before newSession"
if (hasCabalChanged){
getBuildFlags(file);
hasCabalChanged=false;
}
long t0=System.currentTimeMillis();
expression=expression.replace('\n', ' ').replace('\r',' ');
JSONArray arr=null;
BWFileInfo bfi=getFileInfo(file);
if (processManager.startRunningFile(bfi)){
try {
BWProcessInfo bpi=processManager.getProcess(bfi);
if (bpi!=null){
switchProcess(bpi, bfi, true);
Process p=bpi.getProcess();
//BuildWrapperPlugin.logInfo("getThingAtPoint longrunning start");
String command="e "+expression;
p.getOutputStream().write((command+PlatformUtil.NL).getBytes(FileUtil.UTF8));
p.getOutputStream().flush();
arr=readArrayBW(p);
}
} catch (IOException ioe){
BuildWrapperPlugin.logError(BWText.process_launch_error, ioe);
return Collections.emptyList();
} finally {
//BuildWrapperPlugin.logInfo("getThingAtPoint longrunning end");
processManager.stopRunningFile(bfi);
}
}
if (arr==null){
String path=file.getProjectRelativePath().toOSString();
LinkedList<String> command=new LinkedList<>();
command.add("eval");
command.add("--file="+path);
command.add("--expression="+expression);
addEditorStanza(file,command);
arr=run(command,ARRAY);
}
List<EvalResult> ers=new ArrayList<>();
if (arr!=null){
JSONArray locs=arr.optJSONArray(0);
if (locs!=null){
for (int a=0;a<locs.length();a++){
try {
JSONObject o=locs.getJSONObject(a);
ers.add(new EvalResult(o));
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.process_parse_thingatpoint_error, je);
}
}
}
}
if (logBuildTimes){
long t1=System.currentTimeMillis();
BuildWrapperPlugin.logInfo("eval:"+(t1-t0)+"ms ("+ers.size()+")");
}
return ers;
}
/**
* @return the lastBuild
*/
public Long getLastBuild() {
return lastBuild;
}
/**
* @return the lastAddSource
*/
public Set<IProject> getAddSourceProjects() {
return addSourceProjects;
}
}