Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Maven to build complex Flex project

I'm currently looking into replacing our very proprietary build environment with Maven. The current solution is an inhouse development for the central build, and for easier local development I compile inside the Eclipse/Flex IDE and have an ANT script which copies everything together to my local Apache directory (i.e. my ant file does not do the full build and also relies on having all projects locally available).

We're using Eclipse 3.6.1 (Helios) with Flash Builder Plugin (i.e. Flex4), targetting Flex SDK 3.5 (FlashPlayer 10.0) with quite some projects both .swc library projects and .swf modules to be loaded at runtime including resources for each. We also use external linking / runtime shared libraries (with one big sharedlibraries.swf having everything inside, i.e. no need to have the .swc files in the deployment). I've installed Maven 3.0.2, using Sonatypes flexmojos-maven-plugin 3.7.1 (as it seems more active than the one from Servebox). I've manually created pom files for each Eclipse project, to make sure that everything works and also the Eclipse-Maven integration seems to lack Flex support. I've defined appropriate compiler and dependency parameters so that all individual projects compile correctly - i.e. the resulting .swf files do produce a working version of our application. I've also read the "Maven by Example" book from Sonatype and looked at their "Maven: The Complete Reference", while doing lots of googling in parallel.

What I'm struggling with is the final assembly of my project, as well as with additional artifacts (stylesheets). I don't fully understand "the Maven way" and also suspect that Maven is tailored heavily for Java, thus leaving me a bit in the wild with my Flex projects.

Basic structure of my projects:

  • external libraries: I've added those with install:install-file to my repository, no issue here
  • own library projects: they each produce one .swc artifact which can be consumed in other projects, with Maven dependency handling working correctly, no issue here
  • own swf module projects: our application loads swf files at runtime which in turn have some additional resources they need (stylesheet.swf files, localized strings as individual .xlf files, config files, etc). It's those I'm struggling with.

Layout of my swf projects:

  • bin-debug/ used by IDE builds, not used / ignored by Maven build
  • src/ the Flex sources (.as and .mxml files)
  • target/ Maven target directory
  • WebContent/ a directory with suplementary files which needs to be copied to the destination artifact (directory or zip file) verbatim, alongside the .swf artifact
  • styles/ Flex .css files which needs to be compiled into .swf stylesheet files, those are additional artifacts (one for each .css, for different themes) to the main project swf and the WebContent/ content

I'm using a flat hierarchy to work in Eclipse, i.e. my master pom file is placed in a folder which is a sibling to all projects folders. Example:

project_swf/src/...
project_swf/WebContent/assets/labels.xlf
project_swf/WebContent/config/config.xml
project_swf/pom.xml
lib_swc/src/...
lib_swc/pom.xml
master_build/pom.xml
master_build/swf-assembly.xml

I've managed to create a 'master build' building all my swc and swf projects using this master pom:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <name>Dragon Master Build</name>
    <description>This project can be used to build and package the client application</description>

    <properties>
        <!-- this needs to be the same as the groupId tag, which unfortunately cannot be accessed directly using ${} notation -->
        <group-id>de.zefiro.maven.example</group-id>

        <!-- parameter to plugin swf files, shown as plugin 'Build Number' in client -->
        <!-- TODO should be a build number on central system -->
        <zefiro.plugin.buildno>local_dev_mvn</zefiro.plugin.buildno>

        <!-- build all SWF files (except sharedlibraries.swf) with external references (Note: Flexmojo abuses the 'scope' concept here, but there is currently no better way) -->
        <zefiro.swf.scope>external</zefiro.swf.scope>
    </properties>

    <groupId>de.zefiro.maven.example</groupId>
    <artifactId>full_build</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <build>
        <sourceDirectory>src/</sourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.sonatype.flexmojos</groupId>
                <artifactId>flexmojos-maven-plugin</artifactId>
                <version>3.7.1</version>
                <extensions>true</extensions>
                <!-- We need to compile with Flex SDK 3.5 -->
                <dependencies><dependency>
                    <groupId>com.adobe.flex</groupId>
                    <artifactId>compiler</artifactId>
                    <version>3.5.0.12683</version>
                    <type>pom</type>
                </dependency></dependencies>
                <!-- configuration for the Flex compilation -->
                <configuration>
                    <targetPlayer>10.0.0</targetPlayer>
                    <incremental>false</incremental>
                    <debug>false</debug>
                    <runtimeLocales></runtimeLocales>
                    <locale>en_US</locale>
                    <optimize>true</optimize>
                    <showWarnings>true</showWarnings>
                    <strict>true</strict>
                    <useNetwork>true</useNetwork>
                    <allowSourcePathOverlap>true</allowSourcePathOverlap>
                    <definesDeclaration>
                        <property>
                            <name>ZEFIRO::META_INFO</name>
                            <value>"buildno=${zefiro.plugin.buildno}"</value>
                        </property>
                    </definesDeclaration>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>flex-framework</artifactId>
            <version>3.5.0.12683</version>
            <type>pom</type>
            <exclusions>
                <!-- make sure to exclude the default 'playerglobal' transitive dependency (which would be version 9 for SDK 3.5, however, we want version 10) -->
                <exclusion>
                    <groupId>com.adobe.flex.framework</groupId>
                    <artifactId>playerglobal</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>playerglobal</artifactId>
            <!-- this artifact version must match the flex SDK version used in this project -->
            <version>3.5.0.12683</version>
            <!-- the classifier specifies the target flash player major version (default is 9 for SDK 3.5) -->
            <classifier>10</classifier>
            <type>swc</type>
        </dependency>

        [... some more dependencies needed by all projects ...]
    </dependencies>

    <modules>
        <module>../lib_swc</module>
        <module>../project_swf</module>
        [... calls all my projects ...]
    </modules>

</project>

I can then compile my swc library projects with this simple POM:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>de.zefiro.maven.example</groupId>
        <artifactId>full_build</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../master_build</relativePath>
    </parent>

    <name>Sample library project</name>
    <artifactId>lib_swc</artifactId>
    <packaging>swc</packaging>

    <dependencies>
        <dependency>
            <groupId>${group-id}</groupId>
            <artifactId>some_other_library</artifactId>
            <version>${project.version}</version> <!-- I should probably look into dependency/versioning as well, but for now this works -->
            <!-- Note: no 'Scope' here, swc files will be build "merged into code"-style. Needed as transitive dependencies don't work with Maven/Flex - we use 'external' scope for the final swf projects dependencies -->
            <type>swc</type>
        </dependency>
    </dependencies>

</project>

And my swf projects with this POM:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>de.zefiro.maven.example</groupId>
        <artifactId>full_build</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../master_build</relativePath>
        </parent>

    <name>Sample SWF module</name>
    <artifactId>project_swf</artifactId>
    <packaging>swf</packaging>

    <build>
    <!-- This name needs to be exactly specified, as the runtime will load it as it's specified in the configuration xml -->
        <finalName>SampleProjectModule1</finalName>

        <!-- define the master source file, it's picked up for some projects automatically and must be specified for others. Inherits compiler parameters from parent. -->
        <plugin>
            <groupId>org.sonatype.flexmojos</groupId>
            <artifactId>flexmojos-maven-plugin</artifactId>
            <version>3.7.1</version>
            <configuration>
                <sourceFile>project_swf_master_file.as</sourceFile>
            </configuration>
        </plugin>

        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptors>
                        <descriptor>../master_build/swf-assembly.xml</descriptor>
                    </descriptors>
                </configuration>
                <executions>
                    <execution>
                        <id>WebContent-packaging</id>
                        <phase>package</phase>
                        <goals><goal>single</goal></goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>${group-id}</groupId>
            <artifactId>project_swc</artifactId>
            <version>${project.version}</version>
            <scope>${zefiro.swf.scope}</scope> <!-- tell Flex Mojo that we want to compile using runtime shared libraries (rsl) -->
            <type>swc</type>
        </dependency>
        [...]
    </dependencies>

</project>

It is my understanding that each POM file generates one target file (specified with the packaging tag) and that when using the Flex mojo this needs to be of format swf or swc. I seem to be unable to specifiy multiple targets here and also struggled to get my resource files included (Flex copy-resources copies the html wrapper which I don't need (for each project), the normal resource tag copies the resources, but don't packages them). What is now my current working version is to specify an own assembly descriptor which bundles my target .swf file together with the WebContent/ folder into a zip file. Maven has support for jar/war/bin files, but those seem to expect certain conditions to be true, which don't apply for my Flex projects (e.g. I don't have a web.xml)

swf-assembly.xml:

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
    <id>WebContent</id>
    <formats>
        <format>zip</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <fileSets>
        <!-- Take the base swf artifact (only, not the other stuff which is generated there) -->
        <fileSet>
            <directory>${project.build.directory}</directory>
            <outputDirectory>/</outputDirectory>
            <includes>
                <include>*.swf</include>
            </includes>
        </fileSet>
        <!-- add the WebContent folder -->
        <fileSet>
            <directory>FCPContent</directory>
            <outputDirectory>/</outputDirectory>
        </fileSet>
    </fileSets>
</assembly>

Now I've got the target directory with both the .swf file alone and a zip file containing my .swf file together with my WebContent/ resources. It's also placed in the Nexus repository upon calling 'mvn install', so this looks like a good step forward. Though I'm happy to do it completely different if that's better, as my final goal is to have a single directory (or zip/war file containing it) with some static files in it (our html wrapper and loader swf + configuration.xml), and for each of our swf projects (modules we load at runtime) a directory containing the .swf files and the WebContent/ resources - i.e. the contents of the zip files I'm now able to create for each project.

index.html
loader.swf
config.xml
project_swf/SampleProjectModule1.swf
project_swf/assets/labels.xlf
project_swf/config/config.xml
project2_swf/module2.swf
project2_swf/cool_colors_stylesheet.swf

So my question would be: how do define such a Maven master project which creates the final assembly in this format? If there's no other way, a working ANT file might be acceptable, though I've read that it Maven urges us to stay away from those hacks :)

I also need to compile stylesheet files which are then additional artifacts for the swf projects. I'll post a separate question for this.

like image 842
Zefiro Avatar asked Nov 15 '22 01:11

Zefiro


1 Answers

My solution is a bit complex and feels 'dirty' - I thought there could be a cleaner way, but at least I've got it running now.

Basically, I'm using the Maven-assembly-plugin to create intermediate zip archives ('war' does work equally well) containing the swf executable along with any necessary static assets for each plugin I'm creating (we're using an OSGi-like dynamic loader similar to how Eclipse works, one of the static files is a plugin.xml, others are translation files, none of them can be embedded into the .swf). Maven stores the .swf file in Maven filename notation as primary artifact - which I'm completely disregarding - and my zip archive (with the .swf name according to the finalName tag) alongside. The POM and assembly file for this works as shown in my original question.

I've added a blank assembly project which has the sole goal of depending on and thus including all of my other projects. The output of this project is my final, 'real' artifact, which I can then use for distribution and deployment. Since I didn't want to cluster my Eclipse workspace with too many helper projects I broke the 1:1 relationship of Maven projects and Eclipse projects: my Maven assembly plugin just lives in a subdirectory 'assembly' inside my master_build project. I added a module statement for it in my master pom (master_build/pom.xml) which will be executed in the reactor as last project. (Ordering is done automatically due to dependencies given - if you specify it as first module and it's not executed last, then you're building projects you don't need :)

I also made my assembly projects POM a child of my master POM. This is not necessary, but by doing so I can inherit the Maven properties I've defined. It also inherits the call to the Flex compiler, though since there is nothing to compile here this doesn't harm. YMMV.

Here's the POM:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>de.zefiro.maven.example</groupId>
        <artifactId>full_build</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../</relativePath>
    </parent>


    <groupId>${group-id}</groupId>
    <artifactId>full_assembly</artifactId>
    <name>Final Assembly</name>
    <version>${project.parent.version}</version>
    <packaging>pom</packaging> <!-- note that this is POM here, not war or zip -->

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.2.1</version>
                <configuration>
                    <descriptors>
                        <descriptor>final-assembly.xml</descriptor>
                    </descriptors>
                </configuration>
                <executions>
                    <execution>
                        <id>Final-Packaging</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>de.zefiro.maven.example</groupId>
            <artifactId>wrapper</artifactId>
            <version>1.0</version>
            <type>war</type>
        </dependency>
        <dependency>
            <groupId>${group-id}</groupId>
            <artifactId>project_swf</artifactId>
            <version>${project.version}</version>
            <type>zip</type>
            <classifier>WebContent</classifier> <!-- needs to be the ID of the assembler xml which packaged the zip file -->
        </dependency>
[... all other projects which should be packaged ...]
    </dependencies>
</project>

Following the assembly descriptor. See the assembly descriptor documentation for details.

<assembly
    xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
    <id>FinalAssembly</id>
    <formats>
        <format>war</format> <!-- can be either zip or war, depending on what you wish -->
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <includes>
                <include>de.zefiro.maven.example:wrapper:war:*</include>
            </includes>
            <unpack>true</unpack>
        </dependencySet>
        <dependencySet>
            <includes>
                <include>${group-id}:project_swf:zip:WebContent:${project.version}</include>
            </includes>
            <useProjectAttachments>true</useProjectAttachments>
            <unpack>true</unpack>
            <outputDirectory>sample_project_1</outputDirectory>
        </dependencySet>
[... list the same projects as in the POM file ...]
    </dependencySets>
</assembly>

I've got one archive which I'm just consuming, coming from a different department. It contains the HTML wrapper and the main Flex application which is run (and later loads all my plugins from the subdirectories according to a configuration file and the individual projects plugin.xml metadata). This is placed in the output archive root directory. All other projects are the ones I've created myself and get placed in individual subdirectories, thus I'm not using wildcards here. Since I want a single archive containing all files merged I use the unpack option of the dependencySet.

Note that the ID declared in the asembly descriptor for the individual swf plugin projects is now used as a classifier in the dependency section of the pom file as well as a part of the Maven location in the assembly descriptor. Same for the packacking type, which I've choosen zip here. Since this is only used for the intermediate archives, zip is fine, for the final assembly a different format can be chosen.

Inheriting from the same parent I can reuse my Maven properties, which are also available in the assembly descriptor.

A new project can be added by duplicating an existing project and adding it to:

  • master_build/pom.xml as a module
  • master_build/assembly/pom.xml in the dependencies section
  • master_build/assembly/final-assembly.xml as a dependencySet

The final artifact is a war file containing:

sample_project_1/SampleProjectModule1
sample_project_1/assets/labels.xlf
sample_project_1/config/config.xml
other_data/configuration.xml
application.swf
index.html

I.e. the contents of all my swf projects in their respective subdirectories merged with the contents of my wrapper.war containing the static files.

It can be retrieved from the repository at this location:

de.zefiro.maven.example:full_assembly:war:FinalAssembly:1.0-SNAPSHOT

To compile stylesheets for each plugin I'm using the same basic idea moved down to an individual project level. I'll describe it in the other questions

Open Issue: I cannot produce both debug and ship versions of the files. I could either double the projects and package them together (like I do with my stylesheets) or I could run the entire Maven reactor (for the parent POM and master assembly) twice, with e.g. different groupIds, and thus create two completely separate paths for all involved projects. I've not investigated further on this, though.

like image 196
Zefiro Avatar answered Dec 19 '22 02:12

Zefiro