/* Copyright (C) 2009 Mobile Sorcery AB This program is free software; you can redistribute it and/or modify it under the terms of the Eclipse Public License v1.0. This program 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 Eclipse Public License v1.0 for more details. You should have received a copy of the Eclipse Public License v1.0 along with this program. It is also available at http://www.eclipse.org/legal/epl-v10.html */ package com.mobilesorcery.sdk.core; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.text.DateFormat; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeSet; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.ErrorParserManager; import org.eclipse.cdt.core.IErrorParser; import org.eclipse.cdt.core.IMarkerGenerator; import org.eclipse.cdt.core.model.ICModelMarker; import org.eclipse.cdt.core.resources.ACBuilder; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PlatformUI; import com.mobilesorcery.sdk.core.LineReader.LineAdapter; import com.mobilesorcery.sdk.core.build.BuildSequence; import com.mobilesorcery.sdk.core.build.IBuildStep; import com.mobilesorcery.sdk.core.build.IBuildStepFactory; import com.mobilesorcery.sdk.core.stats.CounterVariable; import com.mobilesorcery.sdk.core.stats.Stats; import com.mobilesorcery.sdk.core.stats.Variables; import com.mobilesorcery.sdk.internal.BuildSession; import com.mobilesorcery.sdk.internal.BuildState; import com.mobilesorcery.sdk.internal.PipeTool; import com.mobilesorcery.sdk.internal.dependencies.CompoundDependencyProvider; import com.mobilesorcery.sdk.internal.dependencies.DependencyManager; import com.mobilesorcery.sdk.internal.dependencies.GCCDependencyProvider; import com.mobilesorcery.sdk.internal.dependencies.IDependencyProvider; import com.mobilesorcery.sdk.internal.dependencies.ProjectResourceDependencyProvider; import com.mobilesorcery.sdk.internal.dependencies.ResourceFileDependencyProvider; import com.mobilesorcery.sdk.profiles.IProfile; /** * The main builder. This builder extends ACBuilder for its implementation of * {@link IMarkerGenerator}. * * @author Mattias Bybro * */ public class MoSyncBuilder extends ACBuilder { public static final String OUTPUT = "Output"; public final static String ID = CoreMoSyncPlugin.PLUGIN_ID + ".builder"; public static final String COMPATIBLE_ID = "com.mobilesorcery.sdk.builder.builder"; public static final String CONSOLE_ID = "com.mobilesorcery.build.console"; /** * The prefix used by property initializers. */ public static final String BUILD_PREFS_PREFIX = "build.prefs:"; public final static String ADDITIONAL_INCLUDE_PATHS = BUILD_PREFS_PREFIX + "additional.include.paths"; public static final String ADDITIONAL_NATIVE_INCLUDE_PATHS = ADDITIONAL_INCLUDE_PATHS + ".native"; public final static String IGNORE_DEFAULT_INCLUDE_PATHS = BUILD_PREFS_PREFIX + "ignore.default.include.paths"; public final static String ADDITIONAL_LIBRARY_PATHS = BUILD_PREFS_PREFIX + "additional.library.paths"; public final static String IGNORE_DEFAULT_LIBRARY_PATHS = BUILD_PREFS_PREFIX + "ignore.default.library.paths"; public static final String DEFAULT_LIBRARIES = BUILD_PREFS_PREFIX + "default.libraries"; public final static String ADDITIONAL_LIBRARIES = BUILD_PREFS_PREFIX + "additional.libraries"; public final static String IGNORE_DEFAULT_LIBRARIES = BUILD_PREFS_PREFIX + "ignore.default.libraries"; public static final String LIB_OUTPUT_PATH = BUILD_PREFS_PREFIX + "lib.output.path"; public static final String APP_OUTPUT_PATH = BUILD_PREFS_PREFIX + "app.output.path"; public static final String DEAD_CODE_ELIMINATION = BUILD_PREFS_PREFIX + "dead.code.elim"; public static final String EXTRA_LINK_SWITCHES = BUILD_PREFS_PREFIX + "extra.link.sw"; public static final String EXTRA_RES_SWITCHES = BUILD_PREFS_PREFIX + "extra.res.sw"; public static final String PROJECT_TYPE = BUILD_PREFS_PREFIX + "project.type"; public static final String PROJECT_TYPE_APPLICATION = "app"; public static final String PROJECT_TYPE_LIBRARY = "lib"; public static final String PROJECT_TYPE_EXTENSION = "ext"; public static final String EXTRA_COMPILER_SWITCHES = BUILD_PREFS_PREFIX + "gcc.switches"; public static final String GCC_WARNINGS = BUILD_PREFS_PREFIX + "gcc.warnings"; public static final String MEMORY_PREFS_PREFIX = BUILD_PREFS_PREFIX + "memory."; public static final String MEMORY_HEAPSIZE_KB = MEMORY_PREFS_PREFIX + "heap"; public static final String MEMORY_STACKSIZE_KB = MEMORY_PREFS_PREFIX + "stack"; public static final String MEMORY_DATASIZE_KB = MEMORY_PREFS_PREFIX + "data"; public static final String USE_DEBUG_RUNTIME_LIBS = BUILD_PREFS_PREFIX + "runtime.debug"; public static final String OUTPUT_TYPE = BUILD_PREFS_PREFIX + "output.static.recompilation"; public static final String OUTPUT_TYPE_INTERPRETED = "interpreted"; public static final String OUTPUT_TYPE_STATIC_RECOMPILATION = "rebuilt"; public static final String OUTPUT_TYPE_NATIVE_COMPILE = "native"; public static final String PROJECT_VERSION = BUILD_PREFS_PREFIX + "app.version"; public static final String APP_NAME = BUILD_PREFS_PREFIX + "app.name"; public static final String EXTENSIONS = BUILD_PREFS_PREFIX + "ext"; public static final String REBUILD_ON_ERROR = BUILD_PREFS_PREFIX + "rebuild.on.error"; private static final String APP_CODE = "app.code"; public static final String VERBOSE_BUILDS = "verbose.builds"; private static final String CONSOLE_PREPARED = "console.prepared"; public static final int GCC_WALL = 1 << 1; public static final int GCC_WEXTRA = 1 << 2; public static final int GCC_WERROR = 1 << 3; private static Boolean doesRescompilerLibExist; public final class GCCLineHandler extends LineAdapter { private final ErrorParserManager epm; public GCCLineHandler(ErrorParserManager epm) { this.epm = epm; } private String aggregateLine = ""; @Override public void newLine(String line) { // We need to 'aggregate' lines; // whenever a line is indented, // it is actually an extension // of last line - it's a hack to reconcile // the CDT error parser and the 'real world' if (isLineIndented(line)) { aggregateLine += " " + line.trim(); } else { reportLine(aggregateLine); aggregateLine = line.trim(); } } private boolean isLineIndented(String line) { return line.startsWith(Util.fill(' ', 2)); } public void reportLine(String line) { try { // Kind of backwards..., first we remove a \n, and // now // we add it, so the ErrorParserManager can remove // it again... if (epm != null) { // Write directly to emp, ignore // getoutputstream. epm.write((line + '\n').getBytes()); } } catch (IOException e) { // Ignore. } } @Override public void stop(IOException e) { newLine(""); /* * if (aggregateLine.length() > 0) { reportLine(aggregateLine); } */ } public void reset() { stop(null); aggregateLine = ""; } } public MoSyncBuilder() { } /** * <p> * Returns a fragment/prefix/suffix that may be used in file names, based on * the specifiers of a given variant. * </p> * * @see {@link IBuildVariant#getSpecifiers()} * @param variant * @return The same set of specifiers will always return the same string. * (Usually in some alphabetical order). Only the <b>values</b> of * the specifiers will be used to produce the string. */ public static String getSpecifierPathFragment(IBuildVariant variant) { SortedMap<String, String> specifiers = variant.getSpecifiers(); StringBuffer result = new StringBuffer(); if (specifiers != null) { TreeSet<String> sortedSpecifiers = new TreeSet<String>( String.CASE_INSENSITIVE_ORDER); for (Map.Entry<String, String> specifier : specifiers.entrySet()) { if (specifier.getValue().length() > 0) { sortedSpecifiers.add(specifier.getValue()); } } result.append(Util.join(sortedSpecifiers.toArray(), "_")); } return result.toString(); } /** * Determines whether a resource is part of the output directory tree * * @param project * @param res * @return */ public static boolean isInOutput(IProject project, IResource res) { IPath projectRelativePath = res.getProjectRelativePath(); MoSyncProject mosyncProject = MoSyncProject.create(project); String outputFolder = IsReleasePackageTester .getReleasePackageFolder(mosyncProject); boolean isOutputFolder = outputFolder != null && new Path(outputFolder).isPrefixOf(projectRelativePath); boolean isLegacyOutputFolder = new Path(OUTPUT) .isPrefixOf(projectRelativePath); return isLegacyOutputFolder || isOutputFolder; } /** * Converts a project-relative path to an absolute path. * * @param path * @return An absolute path */ public static IPath toAbsolute(IPath root, String pathStr) { Path path = new Path(pathStr); return root.append(path); } private static IPath getFinalOutputPath(IProject project, IBuildVariant variant) { IProfile targetProfile = variant.getProfile(); if (targetProfile == null) { // Just to avoid NPEs targetProfile = MoSyncProject.create(project).getTargetProfile(); } String outputPath = getPropertyOwner(MoSyncProject.create(project), variant.getConfigurationId()).getProperty(APP_OUTPUT_PATH); if (outputPath == null) { throw new IllegalArgumentException("No output path specified"); } String variantPath = targetProfile.getName(); String specifierSuffix = getSpecifierPathFragment(variant); if (!Util.isEmpty(specifierSuffix)) { variantPath += "_" + specifierSuffix; } return toAbsolute(project.getLocation().append(OUTPUT), outputPath) .append(targetProfile.getVendor().getName()) .append(variantPath); } public static IPath getOutputPath(IProject project, IBuildVariant variant) { return getFinalOutputPath(project, variant); } public static IPath getProgramOutputPath(IProject project, IBuildVariant variant) { return getOutputPath(project, variant).append("program"); } public static IPath getProgramCombOutputPath(IProject project, IBuildVariant variant) { return getOutputPath(project, variant).append("program.comb"); } public static IPath getResourceOutputPath(IProject project, IBuildVariant variant) { return getOutputPath(project, variant).append("resources"); } public static IPath getPackageOutputPath(IProject project, IBuildVariant variant) { return getFinalOutputPath(project, variant).append("package"); } public static String getExtraCompilerSwitches(MoSyncProject project, IBuildVariant variant) throws ParameterResolverException { ParameterResolver resolver = createParameterResolver(project, variant); IPropertyOwner buildProperties = getPropertyOwner(project, variant.getConfigurationId()); return Util.replace(buildProperties .getProperty(MoSyncBuilder.EXTRA_COMPILER_SWITCHES), resolver); } @Override protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { IProject project = getProject(); if (MoSyncProject.NULL_DEPENDENCY_STRATEGY == PropertyUtil.getInteger( MoSyncProject.create(project), MoSyncProject.DEPENDENCY_STRATEGY, MoSyncProject.GCC_DEPENDENCY_STRATEGY)) { // TODO: At this point, we only have a GCC dependency strategy and a // "always full build" strategy kind = FULL_BUILD; } IBuildVariant variant = getActiveVariant(MoSyncProject.create(project)); IBuildSession session = createIncrementalBuildSession(project, kind); if (kind == FULL_BUILD) { build(project, session, variant, null, monitor); } else { incrementalBuild(project, session, variant, null, monitor); } Set<IProject> dependencies = CoreMoSyncPlugin.getDefault() .getProjectDependencyManager(ResourcesPlugin.getWorkspace()) .getDependenciesOf(project); dependencies.add(project); return dependencies.toArray(new IProject[dependencies.size()]); } private boolean hasErrorMarkers(IProject project) throws CoreException { return hasErrorMarkers(project, IResource.DEPTH_INFINITE); } private boolean hasErrorMarkers(IProject project, int depth) throws CoreException { return project.findMaxProblemSeverity( ICModelMarker.C_MODEL_PROBLEM_MARKER, true, depth) == IMarker.SEVERITY_ERROR; } @Override public void clean(IProgressMonitor monitor) { forgetLastBuiltState(); IProject project = getProject(); MoSyncProject mosyncProject = MoSyncProject.create(project); IBuildVariant variant = getActiveVariant(mosyncProject); clean(project, variant, monitor); } public void clean(IProject project, IBuildVariant variant, IProgressMonitor monitor) { IPath output = getOutputPath(project, variant); File outputFile = output.toFile(); IProcessConsole console = CoreMoSyncPlugin.getDefault().createConsole( CONSOLE_ID); prepareConsole(null, console); // We use a null monitor to avoid overloading the UI thread. IProgressMonitor subMonitor = new NullProgressMonitor(); console.addMessage(createBuildMessage("Cleaning", MoSyncProject.create(project), variant)); Util.deleteFiles(getPackageOutputPath(project, variant).toFile(), null, 512, subMonitor); Util.deleteFiles(getProgramOutputPath(project, variant).toFile(), null, 1, subMonitor); Util.deleteFiles(getProgramCombOutputPath(project, variant).toFile(), null, 1, subMonitor); Util.deleteFiles(getResourceOutputPath(project, variant).toFile(), null, 1, subMonitor); Util.deleteFiles(outputFile, Util.getExtensionFilter("s"), 512, subMonitor); IBuildState buildState = MoSyncProject.create(project).getBuildState( variant); buildState.clear(); buildState.save(); buildState.setValid(true); } /** * Perfoms a full build of a project * * @param project * @param variant * @param doClean * @param doBuildResources * @param doPack * @param resourceFilter * @param doLink * @param saveDirtyEditors * @param monitor * @return * @throws CoreException * @throws {@link OperationCanceledException} If the operation was canceled */ public IBuildResult build(IProject project, IBuildSession session, IBuildVariant variant, IFilter<IResource> resourceFilter, IProgressMonitor monitor) throws CoreException, OperationCanceledException { try { // TODO: Allow for setting build config explicitly! monitor.beginTask(MessageFormat.format("Full build of {0}", project.getName()), 8); if (session.doClean()) { clean(project, variant, new SubProgressMonitor(monitor, 1)); } else { monitor.worked(1); } if (!session.doClean() && session.doSaveDirtyEditors()) { if (!saveAllEditors(project)) { throw new OperationCanceledException(); } } return incrementalBuild(project, session, variant, resourceFilter, monitor); } finally { monitor.done(); } } IBuildResult build(IProject project, IResourceDelta[] ignoreMe, IBuildSession session, IBuildVariant variant, IFilter<IResource> resourceFilter, IProgressMonitor monitor) { return null; } /** * Returns the pipe tool mode that should be used for the given kind of * input. * * @param profile * The profile we want to use pipe-tool for. * @param isLib * Indicates whether we are building a library or not. * @return The appropriate mode for the given profile. * @throws CoreException */ public static String getPipeToolMode(MoSyncProject project, IProfile profile, boolean isLib) throws CoreException { if (isLib) { return PipeTool.BUILD_LIB_MODE; } if (project.getProfileManagerType() == MoSyncTool.LEGACY_PROFILE_TYPE) { Map<String, Object> properties = profile.getProperties(); if (properties.containsKey("MA_PROF_OUTPUT_CPP")) { return PipeTool.BUILD_GEN_CPP_MODE; } else if (properties.containsKey("MA_PROF_OUTPUT_JAVA")) { return PipeTool.BUILD_GEN_JAVA_MODE; } else if (properties.containsKey("MA_PROF_OUTPUT_CS")) { return PipeTool.BUILD_GEN_CS_MODE; } } // The default mode. return profile.getPackager().getGenerateMode(profile); } IBuildResult incrementalBuild(IProject project, IBuildSession session, IBuildVariant variant, IFilter<IResource> resourceFilter, IProgressMonitor monitor) throws CoreException { IProcessConsole console = createConsole(session); IBuildResult result = incrementalBuild0(project, session, variant, resourceFilter, console, monitor); if (monitor.isCanceled()) { console.addMessage(IProcessConsole.ERR, "*** Build was cancelled by user ***"); } return result; } IBuildResult incrementalBuild0(IProject project, IBuildSession session, IBuildVariant variant, IFilter<IResource> resourceFilter, IProcessConsole console, IProgressMonitor monitor) throws CoreException { if (CoreMoSyncPlugin.getDefault().isDebugging()) { CoreMoSyncPlugin.trace("Building project {0}", project); } BuildResult buildResult = new BuildResult(project); buildResult.setVariant(variant); Calendar timestamp = Calendar.getInstance(); buildResult.setTimestamp(timestamp.getTimeInMillis()); MoSyncProject mosyncProject = MoSyncProject.create(project); IBuildState buildState = mosyncProject.getBuildState(variant); // STATS. Variables vars = Stats.getStats().getVariables(); vars.get(CounterVariable.class, "builds").inc(); String templateId = mosyncProject .getProperty(MoSyncProject.TEMPLATE_ID); if (templateId != null) { vars.get(CounterVariable.class, "builds-template-" + templateId) .inc(); } if (mosyncProject.getProfileManagerType() == MoSyncTool.DEFAULT_PROFILE_TYPE) { String family = variant.getProfile().getVendor().getName(); vars.get(CounterVariable.class, "builds-platform-" + family).inc(); } ParameterResolver resolver = createParameterResolver(mosyncProject, variant); ensureOutputIsMarkedDerived(project, variant); ErrorParserManager epm = createErrorParserManager(project); CoreException errorToShowInConsole = null; try { /* Set up build monitor */ monitor.beginTask(MessageFormat.format("Building {0}", project), 4); monitor.setTaskName("Clearing old problem markers"); if (session.doLink() && !session.doBuildResources()) { throw new CoreException( new Status(IStatus.ERROR, CoreMoSyncPlugin.PLUGIN_ID, "If resource building is suppressed, then linking should also be.")); } // And we only remove things that are on the project. IFileTreeDiff diff = createDiff(buildState, session); if (PropertyUtil.getBoolean(mosyncProject, REBUILD_ON_ERROR) && hasErrorMarkers(project)) { // Build all files console.addMessage(IProcessConsole.ERR, "*** Errors in previous build triggered full rebuild ***"); diff = null; } if (diff == null || !buildState.isValid()) { buildState.getDependencyManager().clear(); } buildResult.setDependencyDelta(buildState.getDependencyManager() .createDelta()); IPropertyOwner buildProperties = MoSyncBuilder.getPropertyOwner( mosyncProject, variant.getConfigurationId()); DateFormat dateFormater = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.LONG); console.addMessage("Build started at " + dateFormater.format(timestamp.getTime())); console.addMessage(createBuildMessage("Building", mosyncProject, variant)); GCCLineHandler linehandler = new GCCLineHandler(epm); /* Set up pipe-tool */ PipeTool pipeTool = new PipeTool(); pipeTool.setAppCode(getCurrentAppCode(session)); pipeTool.setProject(project); pipeTool.setVariant(variant); pipeTool.setConsole(console); pipeTool.setLineHandler(linehandler); pipeTool.setArguments(buildProperties); IDependencyProvider<IResource> dependencyProvider = createDependencyProvider( mosyncProject, variant); boolean requiresPrivilegedAccess = requiresPrivilegedAccess(mosyncProject); if (requiresPrivilegedAccess) { PrivilegedAccess.getInstance().assertAccess(mosyncProject); } BuildSequence sequence = new BuildSequence(mosyncProject); List<IBuildStep> buildSteps = sequence.getBuildSteps(session); monitor.beginTask("Build", buildSteps.size()); sequence.assertValid(session); int continueFlag = IBuildStep.CONTINUE; for (IBuildStep buildStep : buildSteps) { long startTime = System.currentTimeMillis(); if (monitor.isCanceled()) { return buildResult; } console.addMessage(IProcessConsole.MESSAGE, MessageFormat.format("Performing build step ''{0}''", buildStep.getName())); if (continueFlag != IBuildStep.SKIP && buildStep.shouldBuild(mosyncProject, session, buildResult)) { if (CoreMoSyncPlugin.getDefault().isDebugging()) { CoreMoSyncPlugin.trace( "Performing build step {0} for project {1}", buildStep.getName(), buildResult.getProject() .getName()); } buildStep.initConsole(console); buildStep.initBuildProperties(buildProperties); buildStep.initBuildState(buildState); buildStep.initPipeTool(pipeTool); buildStep.initParameterResolver(resolver); buildStep.initDefaultLineHandler(linehandler); buildStep.initDependencyProvider(dependencyProvider); buildStep.initResourceFilter(resourceFilter); continueFlag = buildStep.incrementalBuild(mosyncProject, session, variant, diff, buildResult, monitor); if (continueFlag == IBuildStep.SKIP) { console.addMessage(MessageFormat .format("Was told by build step {0} to skip the remaining build steps. Build successful.", buildStep.getName())); } console.addMessage(MessageFormat.format("Time for buildstep {0}: {1}.", buildStep.getName(), Util.elapsedTime(System.currentTimeMillis() - startTime))); } monitor.worked(1); } CoreException buildError = buildResult.createException(); if (buildError != null) { throw buildError; } // Update the current set of dependencies. buildState.getDependencyManager().applyDelta( buildResult.getDependencyDelta()); Date endTimestamp = Calendar.getInstance().getTime(); console.addMessage(MessageFormat.format("Build finished at {0}. (Build time: {1}.)", dateFormater.format(endTimestamp), Util.elapsedTime(endTimestamp.getTime() - timestamp.getTime().getTime()))); refresh(project); buildResult.setSuccess(true); if (CoreMoSyncPlugin.getDefault().isDebugging()) { CoreMoSyncPlugin.trace("Changed dependencies:\n{0}", buildResult.getDependencyDelta()); CoreMoSyncPlugin.trace("Current set of dependencies:\n{0}", buildState.getDependencyManager()); } } catch (OperationCanceledException e) { // That's ok, why? MB 11-01-10: Because :) return buildResult; } catch (CoreException e) { errorToShowInConsole = e; } catch (Exception e) { errorToShowInConsole = new CoreException(new Status(IStatus.ERROR, CoreMoSyncPlugin.PLUGIN_ID, e.getMessage(), e)); } finally { epm.reportProblems(); if (!monitor.isCanceled() && !buildResult.success() && !hasErrorMarkers(project)) { addBuildFailedMarker(project); } else if (buildResult.success()) { clearCMarkers(project); } saveBuildState(buildState, mosyncProject, buildResult); if (errorToShowInConsole != null) { console.addMessage(IProcessConsole.ERR, errorToShowInConsole.getMessage()); throw errorToShowInConsole; } } return buildResult; } public static void refresh(IResource resource) throws CoreException { if (!CoreMoSyncPlugin.isHeadless()) { resource.refreshLocal(IProject.DEPTH_INFINITE, new NullProgressMonitor()); } } public static boolean requiresPrivilegedAccess(MoSyncProject mosyncProject) { BuildSequence seq = new BuildSequence(mosyncProject); return requiresPrivilegedAccess(seq); } public static boolean requiresPrivilegedAccess(BuildSequence seq) { List<IBuildStepFactory> factories = seq.getBuildStepFactories(); for (IBuildStepFactory factory : factories) { if (factory.requiresPrivilegedAccess()) { return true; } } return false; } public static boolean hasBuildState(IResource location) { return BuildState.hasBuildState(location); } public static IBuildState parseBuildState(IResource location) { return BuildState.parseBuildState(location); } private IDependencyProvider<IResource> createDependencyProvider( MoSyncProject mosyncProject, IBuildVariant variant) { CompoundDependencyProvider<IResource> dependencyProvider = new CompoundDependencyProvider<IResource>( new GCCDependencyProvider(mosyncProject, variant), new ProjectResourceDependencyProvider(mosyncProject .getWrappedProject(), variant), new ResourceFileDependencyProvider(mosyncProject, variant)); return dependencyProvider; } /** * Creates a {@link ParameterResolver} for a project * * @param project * @param variant * The variant to create the resolver for, or <code>null</code> * for the active, non-finalizing variant. * @return */ public static ParameterResolver createParameterResolver( MoSyncProject project, IBuildVariant variant) { return MoSyncProjectParameterResolver.create(project, variant); } public static IPath[] resolvePaths(IPath[] paths, ParameterResolver resolver) throws ParameterResolverException { // TODO: Consider moving this method IPath[] result = new IPath[paths.length]; for (int i = 0; i < paths.length; i++) { result[i] = new Path(Util.replace(paths[i].toString(), resolver)); } return result; } public static String getCurrentAppCode(IBuildSession session) { // TODO: This will result in *most* equivalent apps to share // app code across devices; in particular finalization will always // share app code. String appCode = (String) session.getProperties().get(APP_CODE); if (Util.isEmpty(appCode)) { appCode = PipeTool.generateAppCode(); session.getProperties().put(APP_CODE, appCode); } return appCode; } private String createBuildMessage(String buildType, MoSyncProject project, IBuildVariant variant) { StringBuffer result = new StringBuffer(); result.append(MessageFormat.format("{0}: Project {1} for profile {2}", buildType, project.getName(), variant.getProfile())); if (project.areBuildConfigurationsSupported() && variant.getConfigurationId() != null) { result.append(MessageFormat.format("\nConfiguration: {0}", variant.getConfigurationId())); } return result.toString(); } // returns null if a full build should be performed. private IFileTreeDiff createDiff(IBuildState buildState, IBuildSession session) throws CoreException { Set<String> changedProperties = buildState.getChangedBuildProperties(); if (session.doClean() || buildState.fullRebuildNeeded()) { // Always full build after clean. return null; } // Also, if the variant we want to build does not match what was // previously built here, // then we'll do a full rebuild. IBuildResult previousResult = buildState.getBuildResult(); if (previousResult == null || !Util.equals(buildState.getBuildVariant(), previousResult.getVariant())) { return null; } if (changedProperties.isEmpty()) { return buildState.createDiff(); } else { if (CoreMoSyncPlugin.getDefault().isDebugging()) { CoreMoSyncPlugin .trace("Changed build properties, full rebuild required. Changed properties: {0}", changedProperties); } return null; } } private void ensureOutputIsMarkedDerived(IProject project, IBuildVariant variant) throws CoreException { ensureFolderIsMarkedDerived(project.getFolder(OUTPUT)); IPath outputPath = getOutputPath(project, variant); IContainer[] outputFolder = project.getWorkspace().getRoot() .findContainersForLocation(outputPath); for (int i = 0; i < outputFolder.length; i++) { ensureFolderIsMarkedDerived((IFolder) outputFolder[i]); } } private static void ensureFolderExists(IFolder folder) throws CoreException { if (!folder.exists()) { if (!folder.getParent().exists() && folder.getParent().getType() == IResource.FOLDER) { ensureFolderExists((IFolder) folder.getParent()); } folder.create(true, true, new NullProgressMonitor()); } } public static void ensureFolderIsMarkedDerived(IFolder folder) throws CoreException { ensureFolderExists(folder); if (!folder.isDerived()) { folder.setDerived(true); } } private void saveBuildState(IBuildState buildState, MoSyncProject project, IBuildResult buildResult) throws CoreException { buildState.updateResult(buildResult); buildState.updateState(project.getWrappedProject()); buildState.updateBuildProperties(project.getProperties()); buildState.fullRebuildNeeded(buildResult == null || !buildResult.success()); buildState.save(); buildState.setValid(true); } private void addBuildFailedMarker(IProject project) throws CoreException { // Ensure there is a build failed marker if the build failed; will cause // all failed builds to // be completely rebuilt later. IMarker marker = project .createMarker(ICModelMarker.C_MODEL_PROBLEM_MARKER); marker.setAttribute(IMarker.MESSAGE, "Last build failed"); marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); } public static IPropertyOwner getPropertyOwner(MoSyncProject mosyncProject, String configId) { return getPropertyOwner(mosyncProject, mosyncProject.getBuildConfiguration(configId)); } static IPropertyOwner getPropertyOwner(MoSyncProject mosyncProject, IBuildConfiguration config) { return mosyncProject.areBuildConfigurationsSupported() && config != null ? config.getProperties() : mosyncProject; } /** * 'Prepares' a console for printing -- first checks whether the CDT * preference is set to clear the console, and if so, clears it. * * @param console */ private void prepareConsole(IBuildSession session, IProcessConsole console) { boolean needsPreparing = !CoreMoSyncPlugin.isHeadless(); needsPreparing &= (session != null && session.getProperties().get( CONSOLE_PREPARED) == null); if (needsPreparing) { console.prepare(); if (session != null) { session.getProperties().put(CONSOLE_PREPARED, Boolean.TRUE.toString()); } } } /** * Creates a console that is ready for printing. * * @param session * The current build session. * @return A console that is ready for printing. */ private IProcessConsole createConsole(IBuildSession session) { IProcessConsole console = CoreMoSyncPlugin.getDefault().createConsole( CONSOLE_ID); prepareConsole(session, console); if (!MoSyncTool.getDefault().isValid()) { String error = MoSyncTool.getDefault().validate(); console.addMessage(MessageFormat.format( "MoSync Tool not properly initialized: {0}", error)); console.addMessage("- go to Window > Preferences > MoSync Tool to set the MoSync home directory"); } return console; } // TODO: REMOVE! protected Set<IProject> computeProjectDependencies( IProgressMonitor monitor, MoSyncProject mosyncProject, IBuildState buildState, IResource[] allAffectedResources) { IProject project = mosyncProject.getWrappedProject(); monitor.setTaskName(MessageFormat.format( "Computing project dependencies for {0}", project.getName())); DependencyManager<IProject> projectDependencies = CoreMoSyncPlugin .getDefault().getProjectDependencyManager( ResourcesPlugin.getWorkspace()); projectDependencies.clearDependencies(project); HashSet<IProject> allProjectDependencies = new HashSet<IProject>(); Set<IResource> dependencies = buildState.getDependencyManager() .getDependenciesOf(Arrays.asList(allAffectedResources)); for (IResource resourceDependency : dependencies) { if (resourceDependency.getType() != IResource.ROOT) { allProjectDependencies.add(resourceDependency.getProject()); } } // No deps on self allProjectDependencies.remove(project); if (CoreMoSyncPlugin.getDefault().isDebugging()) { CoreMoSyncPlugin .trace(MessageFormat .format("Computed project dependencies. Project {0} depends on {1}", project.getName(), allProjectDependencies)); } return allProjectDependencies; } public static IPath computeLibraryOutput(MoSyncProject mosyncProject, IPropertyOwner buildProperties) { String outputPath = buildProperties.getProperty(LIB_OUTPUT_PATH); if (outputPath == null) { throw new IllegalArgumentException("Library path is not specified"); } return toAbsolute(mosyncProject.getWrappedProject().getLocation() .append(OUTPUT), outputPath); } /** * Clears all C markers of the given resource * * @param resource * @param severityError * @return true if at least one marker was cleared, false otherwise * @throws CoreException * @throws CoreException */ public static boolean clearCMarkers(IResource resource) throws CoreException { return clearCMarkers(resource, IMarker.SEVERITY_INFO); } public static boolean clearCMarkers(IResource resource, int severity) throws CoreException { if (!resource.exists()) { return false; } IMarker[] markers = resource.findMarkers( ICModelMarker.C_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE); IMarker[] toBeRemoved = markers; if (severity != IMarker.SEVERITY_INFO) { ArrayList<IMarker> toBeRemovedList = new ArrayList<IMarker>(); for (int i = 0; i < markers.length; i++) { Object severityOfMarker = markers[i] .getAttribute(IMarker.SEVERITY); if (severityOfMarker instanceof Integer) { if (((Integer) severityOfMarker) >= severity) { toBeRemovedList.add(markers[i]); } } } toBeRemoved = toBeRemovedList.toArray(new IMarker[0]); } resource.getWorkspace().deleteMarkers(toBeRemoved); return toBeRemoved.length > 0; } private ErrorParserManager createErrorParserManager(IProject project) { String epId = CCorePlugin.PLUGIN_ID + ".GCCErrorParser"; IErrorParser[] gccEp = CCorePlugin.getDefault().getErrorParser(epId); if (gccEp.length < 1) { return null; // No error parser for gcc available. } else { return new ErrorParserManager(project, this, new String[] { epId }); } } /** * Filters out additional include paths to make it buildable * using native builders * @param project * @param variant * @return */ public static Pair<Boolean, List<IPath>> filterNativeIncludePaths(MoSyncProject project, IBuildVariant variant) { String cfg = variant.getConfigurationId(); IPath[] includePaths = PropertyUtil.getPaths(project.getBuildConfiguration(cfg).getProperties(), ADDITIONAL_NATIVE_INCLUDE_PATHS); // null profile is ok, but beware in the future! IPath[] resolvedPaths; try { resolvedPaths = resolvePaths(includePaths, createParameterResolver(project, variant)); } catch (ParameterResolverException e) { resolvedPaths = new IPath[0]; } ArrayList<IPath> result = new ArrayList<IPath>(); for (IPath resolvedPath : resolvedPaths) { // Keep it simple, for the beta we just remove anything // not project-relative. IPath relPath = project.getWrappedProject().getLocation().append(resolvedPath); if (relPath.toFile().exists()) { result.add(resolvedPath); } } boolean changed = result.size() != includePaths.length; return new Pair<Boolean, List<IPath>>(changed, result); } public static IPath[] getBaseIncludePaths(MoSyncProject project, IBuildVariant variant) throws ParameterResolverException { return getBaseIncludePaths(project, variant, false); } public static IPath[] getBaseIncludePaths(MoSyncProject project, IBuildVariant variant, boolean neverIncludeDefaults) throws ParameterResolverException { IPropertyOwner buildProperties = getPropertyOwner(project, variant.getConfigurationId()); IPackager packager = variant.getProfile().getPackager(); String outputType = packager.getOutputType(project); boolean isNativeOutput = MoSyncBuilder.OUTPUT_TYPE_NATIVE_COMPILE.equals(outputType); ArrayList<IPath> result = new ArrayList<IPath>(); boolean ignoreDefault = !isNativeOutput && (neverIncludeDefaults || PropertyUtil.getBoolean(buildProperties, IGNORE_DEFAULT_INCLUDE_PATHS)); if (!ignoreDefault) { result.addAll(Arrays.asList(MoSyncTool.getDefault() .getMoSyncDefaultIncludes(isNativeOutput))); } if (project.getProfileManagerType() == MoSyncTool.LEGACY_PROFILE_TYPE) { result.addAll(Arrays.asList(MoSyncBuilder .getProfileIncludes(variant.getProfile()))); } IPath[] additionalIncludePaths = PropertyUtil.getPaths(buildProperties, isNativeOutput ? ADDITIONAL_NATIVE_INCLUDE_PATHS : ADDITIONAL_INCLUDE_PATHS); for (int i = 0; i < additionalIncludePaths.length; i++) { if (additionalIncludePaths[i].getDevice() == null) { // Then might be project relative path. IPath relativeAdditionalIncludePath = project .getWrappedProject().getLocation() .append(additionalIncludePaths[i]); if (relativeAdditionalIncludePath.toFile().exists()) { additionalIncludePaths[i] = relativeAdditionalIncludePath; } } } if (additionalIncludePaths != null) { result.addAll(Arrays.asList(additionalIncludePaths)); } String[] extensionNames = PropertyUtil.getStrings(buildProperties, MoSyncBuilder.EXTENSIONS); for (String extensionName : extensionNames) { MoSyncExtension extension = MoSyncExtensionManager.getDefault().getExtension(extensionName); if (extension != null) { result.add(extension.getIncludePath()); } } IPath[] paths = resolvePaths(result.toArray(new IPath[0]), createParameterResolver(project, variant)); ArrayList<IPath> filteredPaths = new ArrayList<IPath>(); // Filter out some paths. for (int i = 0; i < paths.length; i++) { IPath path = paths[i]; if (isValidIncludePath(path, outputType)) { filteredPaths.add(path); } } return filteredPaths.toArray(new IPath[0]); } private static boolean isValidIncludePath(IPath path, String outputType) { boolean isNativeOutput = MoSyncBuilder.OUTPUT_TYPE_NATIVE_COMPILE.equals(outputType); if (!isNativeOutput) { return true; } // Native uses their own default paths -- and we actually need to // filter the default path out from the additional paths. IPath[] defaultIncludes = MoSyncTool.getDefault().getMoSyncDefaultIncludes(false); for (IPath defaultInclude : defaultIncludes) { if (defaultInclude.equals(path)) { return false; } } return true; } public static IPath[] getProfileIncludes(IProfile profile) { IPath profilePath = MoSyncTool.getDefault().getProfilePath(profile); return profilePath == null ? new IPath[0] : new IPath[] { profilePath }; } public static IPath[] getLibraryPaths(IProject project, IPropertyOwner buildProperties) { ArrayList<IPath> result = new ArrayList<IPath>(); if (!PropertyUtil.getBoolean(buildProperties, IGNORE_DEFAULT_LIBRARY_PATHS)) { result.addAll(Arrays.asList(MoSyncTool.getDefault() .getMoSyncDefaultLibraryPaths())); } IPath[] additionalLibraryPaths = PropertyUtil.getPaths(buildProperties, ADDITIONAL_LIBRARY_PATHS); if (additionalLibraryPaths != null) { result.addAll(Arrays.asList(additionalLibraryPaths)); } String[] extensions = PropertyUtil.getStrings(buildProperties, MoSyncBuilder.EXTENSIONS); for (String extension : extensions) { String extensionLibraryPath = "%mosync-home%/extensions/" + extension + "/lib"; result.add(new Path(extensionLibraryPath)); } return result.toArray(new IPath[0]); } public static IPath[] getLibraries(MoSyncProject project, IBuildVariant variant, IPropertyOwner buildProperties) { // Ehm, I think I've seen this code elsewhere... ArrayList<IPath> result = new ArrayList<IPath>(); if (!PropertyUtil.getBoolean(buildProperties, IGNORE_DEFAULT_LIBRARIES)) { result.addAll(Arrays.asList(PropertyUtil.getPaths(buildProperties, DEFAULT_LIBRARIES))); } IPath[] additionalLibraries = PropertyUtil.getPaths(buildProperties, ADDITIONAL_LIBRARIES); if (additionalLibraries != null) { result.addAll(Arrays.asList(additionalLibraries)); } String[] extensions = PropertyUtil.getStrings(buildProperties, MoSyncBuilder.EXTENSIONS); for (String extension : extensions) { result.add(new Path(extension + ".lib")); } return result.toArray(new IPath[0]); } /*private static boolean doesRescompilerLibExist() { if (doesRescompilerLibExist == null) { try { doesRescompilerLibExist = MoSyncTool.getDefault() .getMoSyncDefaultLibraryPaths()[0] .append("rescompiler.lib").toFile().exists(); } catch (Exception e) { doesRescompilerLibExist = false; } } return doesRescompilerLibExist; }*/ public static boolean isBuilderPreference(String preferenceKey) { return preferenceKey != null && preferenceKey.startsWith(BUILD_PREFS_PREFIX); } public static IProject getProject(ILaunchConfiguration launchConfig) throws CoreException { String projectName = launchConfig == null ? null : launchConfig.getAttribute( ILaunchConstants.PROJECT, ""); if (Util.isEmpty(projectName)) { return null; } IProject project = ResourcesPlugin.getWorkspace().getRoot() .getProject(projectName); if (!project.exists()) { throw new CoreException(new Status(IStatus.ERROR, CoreMoSyncPlugin.PLUGIN_ID, MessageFormat.format( "Cannot launch: Project {0} does not exist", project.getName()))); } return project; } public static IRunnableWithProgress createBuildJob(final IProject project, final IBuildSession buildSession, final List<IBuildVariant> variantsToBuild) { return new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { monitor.beginTask(MessageFormat.format("Building {0} variants", variantsToBuild.size()), variantsToBuild.size()); for (IBuildVariant variantToBuild : variantsToBuild) { SubProgressMonitor jobMonitor = new SubProgressMonitor( monitor, 1); if (!monitor.isCanceled()) { IRunnableWithProgress buildJob = createBuildJob( project, buildSession, variantToBuild); buildJob.run(jobMonitor); } } } }; } public static IRunnableWithProgress createBuildJob(final IProject project, final IBuildSession session, final IBuildVariant variant) { return new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try { IBuildResult buildResult = new MoSyncBuilder().build( project, session, variant, null, monitor); if (!buildResult.success()) { throw new InvocationTargetException(buildResult.createException()); } } catch (CoreException e) { throw new InvocationTargetException(e, variant.getProfile() + ": " + e.getMessage()); //$NON-NLS-1$ } finally { monitor.done(); } } }; } public static boolean saveAllEditors(IResource resource) { return saveAllEditors(resource, false, false); } public static boolean saveAllEditors(IResource resource, boolean force, boolean confirm) { ArrayList<IResource> resourceList = new ArrayList<IResource>(); resourceList.add(resource); return saveAllEditors(resourceList, force, confirm); } public static boolean saveAllEditors(final List<IResource> resources) { return saveAllEditors(resources, false, false); } public static boolean saveAllEditors(final List<IResource> resources, boolean force, final boolean confirm) { if (CoreMoSyncPlugin.isHeadless()) { return true; } final boolean doSaveAll = force || CoreMoSyncPlugin.getSavePolicy().isSaveAllSet(); final boolean[] result = new boolean[1]; result[0] = true; if (doSaveAll) { Display display = PlatformUI.getWorkbench().getDisplay(); if (display != null) { display.syncExec(new Runnable() { @Override public void run() { if (!CoreMoSyncPlugin.getSavePolicy().saveAllEditors( resources.toArray(new IResource[0]), confirm)) { result[0] = false; } } }); } } return result[0]; } /** * Returns a build variant that represents the currently active project * settings. * * @param isFinalizerBuild * @return */ public static IBuildVariant getActiveVariant(MoSyncProject project) { IBuildConfiguration cfg = project.getActiveBuildConfiguration(); String cfgId = project.areBuildConfigurationsSupported() && cfg != null ? cfg .getId() : null; return new BuildVariant(project.getTargetProfile(), cfgId); } /** * Creates a {@link IBuildVariant} for finalizing, based on a specified * {@link IProfile} and the project's active build configuration. * * @param project * @param profile * @return The created {@link IBuildVariant} */ public static IBuildVariant createVariant(MoSyncProject project, IProfile profile) { IBuildConfiguration cfg = project.getActiveBuildConfiguration(); return getVariant(project, profile, cfg); } /** * Creates a {@link IBuildVariant} for finalizing, based on a specified * {@link IProfile} and a specified {@link IBuildConfiguration}. * * @param project * @param profile * @param cfg * @return The created {@link IBuildVariant} */ public static IBuildVariant getVariant(MoSyncProject project, IProfile profile, IBuildConfiguration cfg) { String cfgId = project.areBuildConfigurationsSupported() && cfg != null ? cfg .getId() : null; return new BuildVariant(profile, cfgId); } public static IBuildSession createFinalizerBuildSession( List<IBuildVariant> variants) { return new BuildSession(variants, BuildSession.DO_LINK | BuildSession.DO_PACK | BuildSession.DO_BUILD_RESOURCES); } public static IBuildSession createCompileOnlySession(IBuildVariant variant) { return new BuildSession(Arrays.asList(variant), 0); } /** * Creates a build session with all build steps except CLEAN * * @param variant * @return */ public static IBuildSession createDefaultBuildSession(IBuildVariant variant) { return new BuildSession(Arrays.asList(variant), BuildSession.ALL - BuildSession.DO_CLEAN); } public static IBuildSession createCleanBuildSession(IBuildVariant variant) { return new BuildSession(Arrays.asList(variant), BuildSession.ALL); } /** * Returns a build session for incremental builds (ie the kind of build * session created upon calls to the <code>build</code> method). * * @param kind * The build kind, for example * <code>IncrementalProjectBuilder.FULL_BUILD</code>. * @return */ public static IBuildSession createIncrementalBuildSession(IProject project, int kind) { IBuildVariant variant = getActiveVariant(MoSyncProject.create(project)); int clean = kind == FULL_BUILD ? BuildSession.DO_CLEAN : 0; return new BuildSession(Arrays.asList(variant), clean | BuildSession.DO_BUILD_RESOURCES | BuildSession.DO_LINK | BuildSession.DO_PACK); } public static IPath getMetaDataPath(MoSyncProject project, IBuildVariant variant) { return getOutputPath(project.getWrappedProject(), variant).append( ".metadata"); } public static boolean isLib(MoSyncProject project) { return PROJECT_TYPE_LIBRARY.equals(project.getProperty(PROJECT_TYPE)); } public static boolean isExtension(MoSyncProject project) { return PROJECT_TYPE_EXTENSION.equals(project.getProperty(PROJECT_TYPE)); } public static boolean isResourceFile(IResource resource) { if (resource.getType() == IResource.FILE) { IFile file = (IFile) resource; String name = file.getName(); return ((name.endsWith(".lst") || name.endsWith(".lstx")) && !name.startsWith("stabs.") && !name.startsWith("~tmpres.")); } return false; } public static File getResourcesDirectory(IProject project) { File resDir = project.getLocation().append("Resources").toFile(); return resDir.exists() && resDir.isDirectory() ? resDir : null; } public static Map<String, String> extractMacroDefinesFromGCCArgs(MoSyncProject project, IBuildVariant variant) { LinkedHashMap<String, String> result = new LinkedHashMap<String, String>(); String extraCompilerSwitchesLine = ""; try { extraCompilerSwitchesLine = MoSyncBuilder.getExtraCompilerSwitches(project, variant); } catch (ParameterResolverException e) { CoreMoSyncPlugin.getDefault().logOnce(e, "qqeeww"); } if (!Util.isEmpty(extraCompilerSwitchesLine)) { String[] extraCompilerSwitches = Util.parseCommandLine(extraCompilerSwitchesLine); for (int i = 0; i < extraCompilerSwitches.length; i++) { String extraCompilerSwitch = extraCompilerSwitches[i]; if (extraCompilerSwitch.startsWith("-D") && extraCompilerSwitch.length() > 2) { String trimmedExtraCompilerSwitch = extraCompilerSwitch.substring(2); String[] keyAndValue = trimmedExtraCompilerSwitch.split("=", 2); String key = keyAndValue[0]; String value = keyAndValue.length > 1 ? keyAndValue[1] : ""; result.put(key, value); } } } return result; } }