blob: eda401234144889693ae3553004df03a88329a18 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.royale.compiler.clients;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.royale.compiler.clients.MXMLJSC.JSTargetType;
import org.apache.royale.compiler.config.Configuration;
import org.apache.royale.compiler.config.ConfigurationValue;
import org.apache.royale.compiler.exceptions.ConfigurationException;
import org.apache.royale.compiler.exceptions.ConfigurationException.CannotOpen;
import org.apache.royale.compiler.internal.config.annotations.Arguments;
import org.apache.royale.compiler.internal.config.annotations.Config;
import org.apache.royale.compiler.internal.config.annotations.RoyaleOnly;
import org.apache.royale.compiler.internal.config.annotations.InfiniteArguments;
import org.apache.royale.compiler.internal.config.annotations.Mapping;
import org.apache.royale.compiler.internal.config.annotations.SoftPrerequisites;
import org.apache.royale.compiler.internal.mxml.MXMLNamespaceMapping;
import com.google.common.collect.ImmutableList;
/**
* The {@link JSConfiguration} class holds all compiler arguments needed for
* compiling ActionScript to JavaScript.
* <p>
* Specific flags are implemented here for the configuration to be loaded by the
* configure() method of {@link MXMLJSC}.
* <p>
* This class inherits all compiler arguments from the MXMLC compiler.
*
* @author Michael Schmalle
*/
public class JSConfiguration extends Configuration
{
public JSConfiguration()
{
setCompilerAllowAbstractClasses(null, true);
setCompilerAllowPrivateConstructors(null, true);
setCompilerAllowImportAliases(null, true);
}
//
// 'compiler.targets' option
//
protected final List<String> targets = new ArrayList<String>();
public List<String> getCompilerTargets()
{
if (targets.size() == 0)
targets.add(JSTargetType.JS_ROYALE.getText());
return targets;
}
/**
* The list of compiler outputs to generate
*/
@Config(allowMultiple = true, isPath = false)
@Mapping({ "compiler", "targets" })
@Arguments("target")
@InfiniteArguments
public void setCompilerTargets(ConfigurationValue cv, String[] targetlist)
{
targets.clear();
for (String target : targetlist)
targets.add(target);
}
//
// 'js-output-type'
//
@Config
@Mapping("js-output-type")
public void setJSOutputType(ConfigurationValue cv, String value)
throws ConfigurationException
{
// ignore if set via compiler.targets
if (targets.size() > 0) return;
targets.clear();
targets.add(value);
}
//
// 'source-map'
//
private boolean sourceMap = false;
public boolean getSourceMap()
{
return sourceMap;
}
@Config
@Mapping("source-map")
public void setSourceMap(ConfigurationValue cv, boolean value)
throws ConfigurationException
{
sourceMap = value;
}
//
// 'js-default-initializers'
//
private boolean jsDefaultInitializers = true;
public boolean getJsDefaultInitializers()
{
return jsDefaultInitializers;
}
@Config
@Mapping("js-default-initializers")
public void setJsDefaultInitializers(ConfigurationValue cv, boolean value)
throws ConfigurationException
{
jsDefaultInitializers = value;
}
//
// 'js-dynamic-access-unknown-members'
//
private boolean jsDynamicAccessUnknownMembers = false;
public boolean getJsDynamicAccessUnknownMembers()
{
return jsDynamicAccessUnknownMembers;
}
/**
* If the definition of a member cannot be resolved, emit dynamic access
* instead of normal member access. Ensures that dynamic members aren't
* renamed.
*
* <code>myObject.memberAccess</code> becomes <code>myObject["memberAccess"]</code>
*/
@Config
@Mapping("js-dynamic-access-unknown-members")
public void setJsDynamicAccessUnknownMembers(ConfigurationValue cv, boolean value)
throws ConfigurationException
{
jsDynamicAccessUnknownMembers = value;
}
//
// 'compiler.js-external-library-path' option
//
private final List<String> jsexternalLibraryPath = new ArrayList<String>();
public List<String> getCompilerJsExternalLibraryPath()
{
return jsexternalLibraryPath;
}
@Config(allowMultiple = true, isPath = true)
@Mapping({ "compiler", "js-external-library-path" })
@Arguments(Arguments.PATH_ELEMENT)
@InfiniteArguments
public void setCompilerJsExternalLibraryPath(ConfigurationValue cv, String[] pathlist) throws ConfigurationException
{
final ImmutableList<String> pathElements = ImmutableList.copyOf(pathlist);
final ImmutableList<String> resolvedPaths = expandTokens(pathElements, locales, cv,
!reportMissingCompilerLibraries);
jsexternalLibraryPath.addAll(resolvedPaths);
}
//
// 'compiler.js-library-path' option
//
private final List<String> jslibraryPath = new ArrayList<String>();
public List<String> getCompilerJsLibraryPath()
{
return jslibraryPath;
}
/**
* Links SWC files to the resulting application SWF file. The compiler only links in those classes for the SWC file
* that are required. You can specify a directory or individual SWC files.
*/
@Config(allowMultiple = true, isPath = true)
@Mapping({ "compiler", "js-library-path" })
@Arguments(Arguments.PATH_ELEMENT)
@InfiniteArguments
@SoftPrerequisites({ "locale", "target-player", "exclude-native-js-libraries" })
public void setCompilerJsLibraryPath(ConfigurationValue cv, String[] pathlist) throws CannotOpen
{
final ImmutableList<String> resolvedPaths = expandTokens(Arrays.asList(pathlist), locales, cv,
!reportMissingCompilerLibraries);
jslibraryPath.addAll(resolvedPaths);
}
/**
* Syntax:<br/>
* <code>-define=&lt;name&gt;,&lt;value&gt;</code> where name is <code>NAMESPACE::name</code> and value is a legal
* definition value (e.g. <code>true</code> or <code>1</code> or <code>!CONFIG::debugging</code>)
*
* Example: <code>-define=CONFIG::debugging,true</code>
*
* In <code>royale-config.xml</code>:<br/>
*
* <pre>
* <royale-config>
* <compiler>
* <define>
* <name>CONFIG::debugging</name>
* <value>true</value>
* </define>
* ...
* </compile>
* </royale-config>
* </pre>
*
* Values:<br/>
* Values are ActionScript expressions that must coerce and evaluate to constants at compile-time. Effectively, they
* are replaced in AS code, verbatim, so <code>-define=TEST::oneGreaterTwo,"1>2"</code> will getCompiler coerced and
* evaluated, at compile-time, to <code>false</code>.
*
* It is good practice to wrap values with double-quotes, so that MXMLC correctly parses them as a single argument:
* <br/>
* <code>-define=TEST::oneShiftRightTwo,"1 >> 2"</code>
*
* Values may contain compile-time constants and other configuration values:<br/>
* <code>-define=CONFIG::bool2,false -define=CONFIG::and1,"CONFIG::bool2 && false" TestApp.mxml</code>
*
* String values on the command-line <i>must</i> be surrounded by double-quotes, and either escape-quoted (
* <code>"\"foo\""</code> or <code>"\'foo\'"</code>) or single-quoted (<code>"'foo'"</code>).
*
* String values in configuration files need only be single- or double- quoted:<br/>
*
* <pre>
* <royale-config>
* <compiler>
* <define>
* <name>NAMES::Organization</name>
* <value>'Apache Software Foundation'</value>
* </define>
* <define>
* <name>NAMES::Application</name>
* <value>"Royale 4.8.0"</value>
* </define>
* ...
* </compile>
* </royale-config>
* </pre>
*
* Empty strings <i>must</i> be passed as <code>"''"</code> on the command-line, and <code>''</code> or
* <code>""</code> in configuration files.
*
* Finally, if you have existing definitions in a configuration file, and you would like to add to them with the
* command-line (let's say most of your build setCompilertings are in the configuration, and that you are adding one
* temporarily using the command-line), you use the following syntax: <code>-define+=TEST::temporary,false</code>
* (noting the plus sign)
*
* Note that definitions can be overridden/redefined if you use the append ("+=") syntax (on the commandline or in a
* user config file, for instance) with the same namespace and name, and a new value.
*
* Definitions cannot be removed/undefined. You can undefine ALL existing definitions from (e.g. from
* royale-config.xml) if you do not use append syntax ("=" or append="false").
*
* IMPORTANT FOR FLASH BUILDER If you are using "Additional commandline arguments" to "-define", don't use the
* following syntax though I suggest it above: -define+=CONFIG::foo,"'value'" The trouble is that FB parses the
* double quotes incorrectly as <"'value'> -- the trailing double-quote is dropped. The solution is to avoid inner
* double-quotes and put them around the whole expression: -define+="CONFIG::foo,'value'"
*/
private Map<String, String> jsconfigVars;
/**
* @return A list of ConfigVars
*/
public Map<String, String> getJsCompilerDefine()
{
if (jsconfigVars != null)
return jsconfigVars;
return super.getCompilerDefine();
}
@Config(advanced = true, allowMultiple = true)
@Arguments({ "name", "value" })
public void setJsCompilerDefine(ConfigurationValue cv, String name, String value) throws ConfigurationException
{
if (jsconfigVars == null)
jsconfigVars = new LinkedHashMap<String, String>();
jsconfigVars.put(name, value);
}
//
// 'output' option
//
private String jsoutput;
@Override
public String getOutput()
{
if (jsoutput != null)
return jsoutput;
return super.getOutput();
}
@Config
@Arguments("filename")
public void setJsOutput(ConfigurationValue val, String output) throws ConfigurationException
{
this.jsoutput = getOutputPath(val, output);
}
/**
* @return JS equivalent of -load-config
*/
public String getJsLoadConfig()
{
return null;
}
//
// 'module-output' option
//
private String moduleoutput;
/**
* if used, the js-debug and js-release folders are calculated by removing
* the folders specified from the output folder. This is useful in some
* cases when using module that are in the same source path as the main app
* as opposed to being in separate projects. For example in TourDeFlex,
* the main app is in the src folder, and a module example may be in
* src/mx/controls/ such as mx/controls/ButtonExample.mxml.
* Without this options, the output might end up in
* src/mx/controls/bin/js-debug and src/mx/controls/bin/js-release when
* it would be better if the output was relative to the main app and go
* in bin/js-debug/mx/controls and bin/js-release/mx/controls. Even
* specifying js-output doesn't work as setting it to the main app's
* bin folder would result in the output .JS going in the same folder
* as the main app instead of being nested in mx/controls. So, by
* setting this option to mx/controls, the compiler will calculate the desired
* folder structure.
*/
public String getModuleOutput()
{
if (moduleoutput != null && moduleoutput.equals("/"))
return null;
return moduleoutput == null ? null : moduleoutput.replace("/", File.separator);
}
@Config
@Arguments("filename")
public void setModuleOutput(ConfigurationValue val, String output) throws ConfigurationException
{
this.moduleoutput = output;
}
/**
* Placeholder. MXMLJSC picks off these values and changes them to load-config for the JS compilers
*/
@Config(allowMultiple = true)
@Arguments("filename")
public void setJsLoadConfig(ConfigurationValue cv, String filename) throws ConfigurationException
{
}
//////////////////////////////////////////////////////////////////////////
// compiler.js-namespaces
//////////////////////////////////////////////////////////////////////////
private List<MXMLNamespaceMapping> jsmanifestMappings;
public List<MXMLNamespaceMapping> getCompilerJsNamespacesManifestMappings()
{
return jsmanifestMappings;
}
/**
* Configures a list of many manifests mapped to a single namespace URI.
* <namespace> <uri>library:adobe/flex/something</uri> <manifest>something-manifest.xml</manifest>
* <manifest>something-else-manifest.xml</manifest> ... </namespace>
*
* @param cfgval The configuration value context.
* @param args A List of values for the namespace element, with the first item expected to be the uri and the
* remaining are manifest paths.
*/
@Config(allowMultiple = true)
@Mapping({ "compiler", "js-namespaces", "namespace" })
@Arguments({ "uri", "manifest" })
@InfiniteArguments
@RoyaleOnly
public void setCompilerJsNamespacesNamespace(ConfigurationValue cfgval, List<String> args)
throws ConfigurationException
{
if (args == null)
throw new ConfigurationException.CannotOpen(null, cfgval.getVar(), cfgval.getSource(), cfgval.getLine());
// allow -compiler.namespaces.namespace= which means don't add
// anything, which matches the behavior of things like -compiler.library-path
// which don't throw an error in this case either.
if (args.isEmpty())
return;
if (args.size() < 2)
throw new ConfigurationException.NamespaceMissingManifest("namespace", cfgval.getSource(),
cfgval.getLine());
if (args.size() % 2 != 0)
throw new ConfigurationException.IncorrectArgumentCount(args.size() + 1, args.size(), cfgval.getVar(),
cfgval.getSource(), cfgval.getLine());
if (jsmanifestMappings == null)
jsmanifestMappings = new ArrayList<MXMLNamespaceMapping>();
for (int i = 0; i < args.size() - 1; i += 2)
{
final String uri = args.get(i);
final String manifestFile = args.get(i + 1);
final String path = resolvePathStrict(manifestFile, cfgval);
jsmanifestMappings.add(new MXMLNamespaceMapping(uri, path));
}
}
//
// 'js-vector-emulation-class' option
//
private String jsVectorEmulationClass = null;
public String getJsVectorEmulationClass()
{
return jsVectorEmulationClass;
}
/**
* The class to use instead of default Vector implementation for handling Vector.
*/
@Config(advanced = true)
public void setJsVectorEmulationClass(ConfigurationValue cv, String b)
{
jsVectorEmulationClass = b;
}
//
// 'js-complex-implicit-coercions'
//
private boolean jsComplexImplicitCoercions = true;
public boolean getJsComplexImplicitCoercions()
{
return jsComplexImplicitCoercions;
}
/**
* Support for including/avoiding more complex implicit assignment coercions
* example
* var array:Array = [new MyClass()];
* var myOtherClass:MyOtherClass = array[0];
*
* In the above example, the compiler will (by default) output an implicit coercion
* that is equivalent in actionscript to:
* var myOtherClass:MyOtherClass = MyOtherClass(array[0]);
*
* By setting this configuration option to false, the implicit coercion code in situations similar to the above
* is not generated (other primitive implicit coercions, such as int/uint/Number/String and Boolean coercions remain)
* This is a global setting for the current source code being compiled, it is possible to leave it on and specifically avoid it via doc
* settings. The doc comment compiler directive for that is: @royalesuppresscompleximplicitcoercion
* Another option is to add the explicit coercions in code and then avoid their output
* via specific @royaleignorecoercion doc comment directives. Doing so however may add extra unwanted output
* in other compiler targets (for example, swf bytecode) if the same source code is shared between targets.
*/
@Config(advanced = true)
@Mapping("js-complex-implicit-coercions")
public void setJsComplexImplicitCoercions(ConfigurationValue cv, boolean value)
throws ConfigurationException
{
jsComplexImplicitCoercions = value;
}
//
// 'js-resolve-uncertain'
//
private boolean jsResolveUncertain = true;
public boolean getJsResolveUncertain()
{
return jsResolveUncertain;
}
/**
* Support for avoiding more overhead of resolving instantiations from
* unknown constructors
* example
* var myClass:Class = String;
* var myString:* = new myClass("test");
*
* In the above example, the compiler will (by default) output
* a call to a Language.resolveUncertain method which wraps the 'new myClass("test")'
*
*
* This normalizes the return value for some primitive constructors, so that (for example)
* strict equality and inequality operators provide the same results between compiler
* targets.
* In situations where it is certain that the resolveUncertain method is not needed,
* this option provides a way to switch it off 'globally' for the current source code being compiled.
* It can also be switched off or on locally using the '@royalesuppressresolveuncertain'
* doc comment compiler directive.
*/
@Config(advanced = true)
@Mapping("js-resolve-uncertain")
public void setJsResolveUncertain(ConfigurationValue cv, boolean value)
throws ConfigurationException
{
jsResolveUncertain = value;
}
//
// 'js-vector-index-checks'
//
private boolean jsVectorIndexChecks = true;
public boolean getJsVectorIndexChecks()
{
return jsVectorIndexChecks;
}
/**
* Support for avoiding more overhead of adding checks into
* assignments via Vector index access
* example
* var myVector:Vector.<int> = new Vector.<int>();
* myVector[0] = 42;
*
* In the above example, the compiler will (by default) wrap
* the '0' inside myVector[0] with a method call on the vector instance
* that checks to see if the index is valid for the Vector it is being used against
*
* This check will throw an error if the index is out of range, and the
* range checking differs if the Vector is 'fixed' or non-'fixed'
*
* In situations where it is certain that the index will always be valid for Vector instance
* being targeted, or where all cases in a given codebase are certain to be valid, it is possible
* to avoid the overhead of this check. This is especially important in loops.
* This config setting affects the global setting for the current source code being compiled.
* It can be adjusted locally within code, using the '@royalesuppressvectorindexcheck'
* doc comment compiler directive.
*/
@Config(advanced = true)
@Mapping("js-vector-index-checks")
public void setJsVectorIndexChecks(ConfigurationValue cv, boolean value)
throws ConfigurationException
{
jsVectorIndexChecks = value;
}
}