/* * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.core.linker; import com.google.gwt.core.ext.LinkerContext; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.linker.AbstractLinker; import com.google.gwt.core.ext.linker.Artifact; import com.google.gwt.core.ext.linker.ArtifactSet; import com.google.gwt.core.ext.linker.CompilationResult; import com.google.gwt.core.ext.linker.EmittedArtifact; import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility; import com.google.gwt.core.ext.linker.LinkerOrder; import com.google.gwt.core.ext.linker.LinkerOrder.Order; import com.google.gwt.core.ext.linker.SelectionProperty; import com.google.gwt.core.ext.linker.Shardable; import com.google.gwt.core.ext.linker.SoftPermutation; import com.google.gwt.core.ext.linker.SymbolData; import com.google.gwt.core.ext.linker.SyntheticArtifact; import com.google.gwt.dev.util.Util; import com.google.gwt.dev.util.collect.HashMap; import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapGeneratorV3; import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapGeneratorV3.ExtensionMergeAction; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.SortedMap; import java.util.regex.Pattern; /** * This Linker exports the symbol maps associated with each compilation result as a private file. * The names of the symbol maps files are computed by appending {@value #STRONG_NAME_SUFFIX} to the * value returned by {@link CompilationResult#getStrongName()}. */ @LinkerOrder(Order.POST) @Shardable public class SymbolMapsLinker extends AbstractLinker { public static final String MAKE_SYMBOL_MAPS = "compiler.useSymbolMaps"; /** * Artifact to record insertions or deletions made to Javascript fragments. */ public static class ScriptFragmentEditsArtifact extends Artifact<ScriptFragmentEditsArtifact> { /** * Operation type performed on script. */ public enum Edit { PREFIX, INSERT, REMOVE } private static class EditOperation { public static EditOperation prefix(String data) { return new EditOperation(Edit.PREFIX, 0, data); } Edit op; int numLines; public EditOperation( Edit op, int lineNumber, String data) { this.op = op; this.numLines = countNewLines(data); } public int getNumLines() { return numLines; } public Edit getOp() { return op; } private int countNewLines(String chunkJs) { int newLineCount = 0; for (int j = 0; j < chunkJs.length(); j++) { if (chunkJs.charAt(j) == '\n') { newLineCount++; } } return newLineCount; } } private List<EditOperation> editOperations = new ArrayList<EditOperation>(); private String strongName; private int fragment; public ScriptFragmentEditsArtifact(String strongName, int fragment) { super(SymbolMapsLinker.class); this.strongName = strongName; this.fragment = fragment; } public int getFragment() { return fragment; } public String getStrongName() { return strongName; } @Override public int hashCode() { return (strongName + fragment).hashCode(); } public void prefixLines(String lines) { editOperations.add(EditOperation.prefix(lines)); } @Override protected int compareToComparableArtifact(SymbolMapsLinker.ScriptFragmentEditsArtifact o) { int result = (strongName + fragment).compareTo(strongName + fragment); return result; } @Override protected Class<ScriptFragmentEditsArtifact> getComparableArtifactType() { return ScriptFragmentEditsArtifact.class; } } /** * Artifact to represent a sourcemap file to be processed by SymbolMapsLinker. */ public static class SourceMapArtifact extends SyntheticArtifact { // This pattern should match sourceMapFilenameForFragment. public static final Pattern isSourceMapFile = Pattern.compile("sourceMap[0-9]+\\.json$"); private int permutationId; private int fragment; private byte[] js; private final String sourceRoot; public SourceMapArtifact(int permutationId, int fragment, byte[] js, String sourceRoot) { super(SymbolMapsLinker.class, permutationId + '/' + sourceMapFilenameForFragment(fragment), js); this.permutationId = permutationId; this.fragment = fragment; this.js = js; this.sourceRoot = sourceRoot; } public int getFragment() { return fragment; } public int getPermutationId() { return permutationId; } /** * The base URL for Java filenames in the sourcemap. * (We need to reapply this after edits.) */ public String getSourceRoot() { return sourceRoot; } public static String sourceMapFilenameForFragment(int fragment) { // If this changes, update isSourceMapFile. return "sourceMap" + fragment + ".json"; } } /** * This value is appended to the strong name of the CompilationResult to form the symbol map's * filename. */ public static final String STRONG_NAME_SUFFIX = ".symbolMap"; public static String propertyMapToString( Map<SelectionProperty, String> propertyMap) { StringWriter writer = new StringWriter(); PrintWriter pw = new PrintWriter(writer); printPropertyMap(pw, propertyMap); pw.flush(); return writer.toString(); } private static void printPropertyMap(PrintWriter pw, Map<SelectionProperty, String> map) { boolean needsComma = false; for (Map.Entry<SelectionProperty, String> entry : map.entrySet()) { if (needsComma) { pw.print(" , "); } else { needsComma = true; } pw.print("'"); pw.print(entry.getKey().getName()); pw.print("' : '"); pw.print(entry.getValue()); pw.print("'"); } } @Override public String getDescription() { return "Export CompilationResult symbol maps"; } /** * Included to support legacy non-shardable subclasses. */ @Override public ArtifactSet link(TreeLogger logger, LinkerContext context, ArtifactSet artifacts) throws UnableToCompleteException { return link(logger, context, artifacts, true); } @Override public ArtifactSet link(TreeLogger logger, LinkerContext context, ArtifactSet artifacts, boolean onePermutation) throws UnableToCompleteException { if (onePermutation) { artifacts = new ArtifactSet(artifacts); Map<Integer, String> permMap = new HashMap<Integer, String>(); Event writeSymbolMapsEvent = SpeedTracerLogger.start(CompilerEventType.WRITE_SYMBOL_MAPS); ByteArrayOutputStream out = new ByteArrayOutputStream(); for (CompilationResult result : artifacts.find(CompilationResult.class)) { boolean makeSymbolMaps = true; for (SoftPermutation perm : result.getSoftPermutations()) { for (Entry<SelectionProperty, String> propMapEntry : perm.getPropertyMap().entrySet()) { if (propMapEntry.getKey().getName().equals(MAKE_SYMBOL_MAPS)) { makeSymbolMaps = Boolean.valueOf(propMapEntry.getValue()); } } } permMap.put(result.getPermutationId(), result.getStrongName()); if (makeSymbolMaps) { PrintWriter pw = new PrintWriter(out); doWriteSymbolMap(logger, result, pw); pw.close(); doEmitSymbolMap(logger, artifacts, result, out); out.reset(); } } writeSymbolMapsEvent.end(); Event writeSourceMapsEvent = SpeedTracerLogger.start(CompilerEventType.WRITE_SOURCE_MAPS); for (SourceMapArtifact se : artifacts.find(SourceMapArtifact.class)) { // filename is permutation_id/sourceMap<fragmentNumber>.json String sourceMapString = Util.readStreamAsString(se.getContents(logger)); String strongName = permMap.get(se.getPermutationId()); String partialPath = strongName + "_sourceMap" + se.getFragment() + ".json"; int fragment = se.getFragment(); ScriptFragmentEditsArtifact editArtifact = null; for (ScriptFragmentEditsArtifact mp : artifacts.find(ScriptFragmentEditsArtifact.class)) { if (mp.getStrongName().equals(strongName) && mp.getFragment() == fragment) { editArtifact = mp; artifacts.remove(editArtifact); break; } } SyntheticArtifact emArt = null; // no need to adjust source map if (editArtifact == null) { emArt = emitSourceMapString(logger, sourceMapString, partialPath); } else { SourceMapGeneratorV3 sourceMapGenerator = new SourceMapGeneratorV3(); if (se.getSourceRoot() != null) { // Reapply source root since mergeMapSection() will not copy it. sourceMapGenerator.setSourceRoot(se.getSourceRoot()); } try { int totalPrefixLines = 0; for (ScriptFragmentEditsArtifact.EditOperation op : editArtifact.editOperations) { if (op.getOp() == ScriptFragmentEditsArtifact.Edit.PREFIX) { totalPrefixLines += op.getNumLines(); } } // TODO(cromwellian): apply insert and remove edits sourceMapGenerator.mergeMapSection(totalPrefixLines, 0, sourceMapString, new ExtensionMergeAction() { @Override public Object merge(String extKey, Object oldVal, Object newVal) { return newVal; } }); StringWriter stringWriter = new StringWriter(); sourceMapGenerator.appendTo(stringWriter, "sourceMap"); emArt = emitSourceMapString(logger, stringWriter.toString(), partialPath); } catch (Exception e) { logger.log(TreeLogger.Type.WARN, "Can't write source map " + partialPath, e); } } artifacts.add(emArt); artifacts.remove(se); } writeSourceMapsEvent.end(); } return artifacts; } /** * Override to change the manner in which the symbol map is emitted. */ protected void doEmitSymbolMap(TreeLogger logger, ArtifactSet artifacts, CompilationResult result, ByteArrayOutputStream out) throws UnableToCompleteException { EmittedArtifact symbolMapArtifact = emitBytes(logger, out.toByteArray(), result.getStrongName() + STRONG_NAME_SUFFIX); // TODO: change to Deploy when possible symbolMapArtifact.setVisibility(Visibility.LegacyDeploy); artifacts.add(symbolMapArtifact); } /** * Override to change the format of the symbol map. * * @param logger the logger to write to * @param result the compilation result * @param pw the output PrintWriter * @throws UnableToCompleteException if an error occurs */ protected void doWriteSymbolMap(TreeLogger logger, CompilationResult result, PrintWriter pw) throws UnableToCompleteException { pw.println("# { " + result.getPermutationId() + " }"); for (SortedMap<SelectionProperty, String> map : result.getPropertyMap()) { pw.print("# { "); printPropertyMap(pw, map); pw.println(" }"); } pw.println("# jsName, jsniIdent, className, memberName, sourceUri, sourceLine, fragmentNumber"); StringBuilder sb = new StringBuilder(1024); char[] buf = new char[1024]; for (SymbolData symbol : result.getSymbolMap()) { sb.append(symbol.getSymbolName()); sb.append(','); String jsniIdent = symbol.getJsniIdent(); if (jsniIdent != null) { sb.append(jsniIdent); } sb.append(','); sb.append(symbol.getClassName()); sb.append(','); String memberName = symbol.getMemberName(); if (memberName != null) { sb.append(memberName); } sb.append(','); String sourceUri = symbol.getSourceUri(); if (sourceUri != null) { sb.append(sourceUri); } sb.append(','); sb.append(symbol.getSourceLine()); sb.append(','); sb.append(symbol.getFragmentNumber()); sb.append('\n'); int sbLen = sb.length(); if (buf.length < sbLen) { int bufLen = buf.length; while (bufLen < sbLen) { bufLen <<= 1; } buf = new char[bufLen]; } sb.getChars(0, sbLen, buf, 0); pw.write(buf, 0, sbLen); sb.setLength(0); } } protected SyntheticArtifact emitSourceMapString(TreeLogger logger, String contents, String partialPath) throws UnableToCompleteException { SyntheticArtifact emArt = emitString(logger, contents, partialPath); emArt.setVisibility(Visibility.LegacyDeploy); return emArt; } }