Proposal For AS3 Conditional Compilation

Proposal For AS3 Conditional Compilation

What Is This Article About?

This article does not describe techniques currently implemented by the the Flash Platform. It is a humble proposal for adding some convenient functionalities to the Flash and Flex compilers.

The purpose here is to shed some light on some relevant issues, due to the evolution of Internet technologies, and to propose solutions to help developers to produce better code.

To this end, the points which will be exposed all concern the modification of the Adobe's ActionScript compilers. Therefore, their implementation can only be considered by Adobe's developers. What we tried to do, is to provide as clear of a description as possible of the tools we need to allow developers to use the Flash Platform as the best development environment for mobile applications.

Consequences Of The Mobile Integration In Flash

Targeting Multiple Platforms With One Application

Since the Flash Platform takes charge of web, desktop and mobile technologies, through both the Flash Player and the AIR Runtime we have to re-think application development. We now need to pay attention to the specificities of each targeted device.

Adobe makes great efforts to ensure that currently, the Flash Player and AIR Runtime are the only tools which are truly cross-platform. But the fact remains that each manufacturer, device, or operating system, provides its own set of functionalities.

Fortunately, the Flash Platform provides efficient solutions, like the native extension bridge, to mitigate this. Thus, it is possible for developers to implement some nice features regarding the targeted device. But in any case, such a distinguishing portion of code cannot be shared over all other systems. So, this is where the different types of code compatibility issues come in.

The Ghost Code Issue

First, let us introduce the principle of Ghost Codes in the machine:

A Ghost Code section specifies one, or many, portion(s) of code which are included in the bytecode, but never used by the application at runtime.

To illustrate this definition, we can consider the following mouse event implementation for a custom button:

private function initCustomBtn():void {
	this.addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
	this.addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
	// rest of the statement here...
}

private mouseOverHandler(e:MouseEvent):void {
	// rest of the statement here...
}

private mouseOutHandler(e:MouseEvent):void {
	// rest of the statement here...
}

As you see, the code is perfectly correct, whether you compile for the Flash Player or the Mobile AIR environment. Maybe a single warning message might tell you that the roll over and roll out events are not supported by AIR mobile applications.

But that is the key point of the Ghost Code issue: we must embed a certain number of unused lines of code according to the application we have developed. And the fact is that we currently have no solution to avoid this issue, except by writing separate applications that include common parts.

Such a solution would seem perfectly adapted at the first view, but it is totally unmanageable if you have to develop for five different platforms, especially when working on huge sofware applications.

The Code Compatibility Issue

As we said before, Flash provides a fantastic set of APIs for embedding and extending the AIR capabilities, according to the targeted device, or platform:

Native Extensions for Adobe AIR are code libraries that contain native code wrapped with an ActionScript API. Native extensions provide easy access to device-specific libraries and features that are not available in the built-in ActionScript classes.

(See Native extensions for Adobe AIR.)

This is a really impressive improvement, which has no equivalent in other "cross-platform" mobile technologies, like for example JavaScript. But again, this raises a new issue. What about the wrapping of native code for both iOS and Android systems, in a multi-platform mobile application? And do not forget that you might want your application to also work on Windows and Mac environments.

Now, developers often choose to embed all of the native extensions in the compiled application and select the correct wrapped utility at runtime, according to the targeted device or operating system. This technique has the inconvenience of breaking two computing principles that should be considered as basic rules for all developers:

  1. Do not include in the program some sections of code which are not compatible with the execution environment.
  2. Do not create some sections of code which are never used by the program. (e.g. The Ghost Code Issue.)

As we mentioned before, the only other solution is to create a base application for each targeted device, which shares some of its implementation with the other distinct applications.

You can now easily understand, why we should think of new and better solutions for improving development of multi-platform applications with ActionScript 3.0.

An Elegant Solution: The Conditional Compilation

Defining Compilation Context Options

The Flash and Flex compilers have several sets of options, such as -keep-as3-metadata, that allow developers to interact with the compilation of a SWF file.

While defining custom metadata, a developer can tell the compiler to have specific behaviors, in relation to its own code. In other words, the use of native metadata (e.g. [Inline] or [Bindable]) has a direct action on the generation of the bytecode at the compilation.

On the same model, to prevent compilation of Ghost Code sections and code compatibility issues, the easiest way is to use the compiler options which specify a context according the portion of code to include in, and/or exclude from, the bytecode.

To do that, the Flash and Flex compilers should be able to follow a simple conditional compilation process, as explained in the next chapter.

The Conditional Compilation Process

The conditional compilation process can be run by adding the -compilation-context option to the compiler command line. The -compilation-context option accepts an extensible list of custom parameters that represent the different context definitions for the conditional compilation. The following example shows how to declare this option with custom arguments:

-compilation-context myContext1 myContext2

Once the context declarations are defined, the compiler can refer to them to identify sections of code to be ignored at compilation and not included in the bytecode. The identification of excluded sections of code is done by the use of only two metadata: [IncludeIn] and [ExcludeFrom].

These two metadata must be implemented by developers in the ActionScript code and allow to cover all exclusion cases.

[IncludeIn] And [ExcludeFrom] Metadata Syntax

Both [IncludeIn] and [ExcludeFrom] metadata tags have the same syntax:

[IncludeIn("myContext")]

[ExcludeFrom("anotherContext")]
  

The context parameter defines the name of the context used to treat the section of code affected by the metadata at compilation. You can specify any number of contexts for a section of code, by separating each context name with a comma:

[IncludeIn("myFirstContext,mySecondContext,...")]

[ExcludeFrom("myThirdContext,myFourthContext,...")]
  

You insert the [IncludeIn] and [ExcludeFrom] metadata tags only before an ActionScript method, whether it is a class or instance member:

//The following statement is valid:
[IncludeIn("myContext")]
private function myMethod():void {
	trace("Hello World");
}

//The following statement is not valid:
[ExcludeFrom("myContext")]
public var myProperty:String = "Hello World";
  

In addition to affecting access to class or instance methods, both metadata tags can be used to affect either public, private, protected or internal scopes. Moreover, we assume that conditional compilation is not appropriate for use with ActionScript getter and setter methods, as well as for custom namespaces.

The [IncludeIn] Metadata

The [IncludeIn] metadata is used to tell the compiler that a method must be excluded from bytecode at compilation, except when the associated context names are defined in the -compilation-context option.

So now, if we have a look at the custom button events implementation that we have discussed in previous chapters, we can imagine a set of rules for inclusion or exclusion in the bytecode:

[IncludeIn("mouseEnabled")]
private function initMouseBtn():void {
	this.addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
	this.addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
	// rest of the statement here...
}

[IncludeIn("mouseEnabled")]
private mouseOverHandler(e:MouseEvent):void {
	// rest of the statement here...
}

[IncludeIn("mouseEnabled")]
private mouseOutHandler(e:MouseEvent):void {
	// rest of the statement here...
}

[IncludeIn("mouseDisabled")]
private function initMouseBtn():void {
	this.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler);
	// rest of the statement here...
}

[IncludeIn("mouseDisabled")]
private touchBeginHandler(e:TouchEvent):void {
	// rest of the statement here...
}

In this way, the mouse events will be included in the bytecode only if the mouseEnabled context is defined in the -compilation-context option: -compilation-context mouseEnabled Whereas the touch event will be included only with the following: -compilation-context mouseDisabled

Thus, we could easily imagine and implement the following statements:

[IncludeIn("android,windows")]
private function initNativeExtAndroid():void {
	// rest of the statement here...
}

[IncludeIn("ios,mac")]
private function initNativeExtIOS():void {
	// rest of the statement here...
}

...and play with the compilation context, as shown below:

-compilation-context mouseDisabled iOS

Such a combination of compilation context options should mean that only the methods defined for iOs, with no mouse events, would be compiled.

The [ExcludeFrom] Metadata

Contrary to the [IncludeIn] metadata, [ExcludeFrom] tells the compiler that a method must be included in the bytecode at compilation, except when the associated context names are defined in the -compilation-context option.

For example, let us consider the implementation of the write() method, as defined by the SPAS 3.0 Console class. If we decide that the production context involves that no trace statement is to be executed at runtime, we would specify the following compilation exclusion rule:

[ExcludeFrom("production")]
public static function write(... arguments):void {
	// rest of the statement here...
}

By indicating to the compiler that the current SWF file is a production application, we ensure that no trace statement will be executed by the console.

-compilation-context production mouseDisabled ios

Note that the rules below should be similar to the preceding ones:

[IncludeIn("debug")]
public static function write(... arguments):void {
	// rest of the statement here...
}
-compilation-context mouseDisabled ios

Using [ExcludeFrom] And [IncludeIn] Metadata Simultaneously

You can use both, [ExcludeFrom] and [IncludeIn] metadata, simultaneously on the same method. For example, the following code defines a test case method for AIR desktop applications:

[IncludeIn("desktop")]
[ExcludeFrom("production")]
public function desktopTestMethod():void {
	// rest of the statement here...
}

According to the behaviors that we have described before, the following compilation context rules would allow access to the desktopTestMethod() method:

-compilation-context desktop debug

Logically, the desktopTestMethod() method is not compiled while using these rules, because both contexts specify combinations of exclusion cases:

-compilation-context web debug
-compilation-context web production

But what would might happen if the context would lead to compiling an application with conflicting behaviors? In the following, the conditional context tells the compiler to include the method while indicating at the same time that it must be excluded:

-compilation-context desktop production

In this case, we assume that the [ExcludeFrom] metadata will always override the [IncludeIn] metadata. Thus, the command line above will render the desktopTestMethod() method inaccessible.

Methods Signatures And References

The [IncludeIn] and [ExcludeFrom] metadata tags are only used to affect method declarations. So, if a method is not included in the bytecode after compilation, due to the use of an exclusion rule, the compiler must remove all of its references contained in the ActionScript code. For example, let us consider the following code:

[ExcludeFrom("production")]
public function sayHello():void {
	trace("Hello World!");
}

public function myMethod():void {
	this.sayHello();
	// rest of the statement here...
}

If we decompile the bytecode after having used the "production" context for creating the SWF file, the code should now look like this:

public function myMethod():void {
	// rest of the statement here...
}

All reference to the sayHello() method should have been omited at compilation.

So now, let us analyse this new implementation of the preceding section:

[ExcludeFrom("production")]
public function sayHello(name:String):String {
	return "Hello " + name;
}

public function myMethod():void {
	this.displayWelcomeMessage(this.sayHello());
	// rest of the statement here...
}

public function displayWelcomeMessage(msg:String):void {
	_myTextField.text = msg;
}

Then, if we create our SWF file by using the "production" context and decompile it, we should obtain the following result:

public function myMethod():void {
	this.displayWelcomeMessage();
	// rest of the statement here...
}

public function displayWelcomeMessage(msg:String):void {
	_myTextField.text = msg;
}

Because no parameter is passed to the displayWelcomeMessage() method, the application will throw an exception each time myMethod() is called.

To avoid this kind of malfunction, a method affected by the the [IncludeIn] and [ExcludeFrom] metadata tags must always specify a return type of void.

Implemetenting Conditional Compilation

As we have seen all along this article, we would have great benefits of using conditional compilation. However, introducing bad practices while working with unusual tools often leads to counterproductive usage. Concerning conditional compilation, we have to introduce good practices to ensure that we will achieve the initial goal, the simplification of multi-platform development, and not the opposite.

Indeed, adding [IncludeIn] and [ExcludeFrom] metadata tags in an anarchic way, anywhere in the code, is a typical example of what a bad practice would look like. If we take the previous argument, about wrapping native extensions, we can easily imagine that delegating this process to a specific object provides the best way to take advantage of conditional compilation:

Anyway, the addition of new tools to a development platform should never be influenced by the way the final developers might use them, either correctly or badly.

Conditional Compilation Rulesets

In this last section, we will discuss all the rule sets needed to correctly implement the process in both, Flash and Flex, compilers.

[ExcludeFrom] And [IncludeIn] Ruleset

In this section, excluded means excluded from bytecode at compilation and included means included in bytecode at compilation.

The following rules define the expected behavior of the [IncludeIn] and [ExcludeFrom] metadata:

  1. The [IncludeIn] metadata indicates that a method must always be excluded, except when the compilation context matches the parameters specified for this metadata.
  2. The [ExcludeFrom] metadata indicates that a method must always be included, except when the compilation context matches the parameters specified for this metadata.
  3. If the options defined in the compilation context lead to contradictory behaviors, while using [IncludeIn] and [ExcludeFrom] metadata simultaneously on a method, the [IncludeIn] metadata tag is ignored.
  4. If an [IncludeIn] or an [ExcludeFrom] metadata is defined for an element other than a method, the compilation will fail.
  5. If the namespace of a method, for which an [IncludeIn] or an [ExcludeFrom] metadata is defined, is different from the public, private, protected or internal namespaces, the compilation will fail.
  6. If a method affected by an [IncludeIn] or an [ExcludeFrom] metadata specifies a return type different from void, the compilation will fail.

Compilation Context Ruleset

  1. If the [IncludeIn] and [ExcludeFrom] metadata parameters match the same compilation context, while using them simultaneously on a method, the compilation will fail.
  2. If a reference to the compilation-context option is not found in the ActionScript code, the compilation will succeed.
  3. If the compilation-context option contains a context name more than once, the compilation will fail.
  4. Allowed characters to define context names of the compilation-context option are alpha-numeric characters only ([a-z]-[A-Z]-[0-9]). If a context name contains unauthorized characters, the compilation will fail.
  5. Context names of the compilation-context option are case-sensitive.

Conclusion

We now have a complete overview of what the "Conditional Compilation Process" for ActionScript compilers could be.

As we mentioned in the introduction, this is just a basic proposal and explanation of the concept. Maybe, one day, this functionality will be a part of the ActionScript compilers capabilities.

Meanwhile, we hope that you enjoyed this article and, if it is effectively the case, we invite you to share it over the social networks, to promote the idea of a Conditional Compilation implementation for the Flash Platform.

Share this article:

Comments:

There are no comments yet for this article.

Leave a reply

Your email is required but will not be displayed.

To prevent abusive emails, please enter the numbers you see in the image below:

Security code