/*
* Copyright 2010-2011 Research In Motion Limited.
*
* 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.
*/
/*
rapc.jar options:
<input>.java
<input>.class
<input>.jar
input files of java classes to include in the output codfile
<input>.jad
input file for application information. i.e. version, name, description, etc...
<directory>
root of a directory tree that should be recursively scanned for .class, .key and resource files
-codename=[<path>/[...]]<filename>
specify output codfile name and location
-library=[<path>/[...]]<filename>
specify output codfile name and location, also that codfile is a library
-listing=[<path>/[...]]<file>.lst
specify output listing file
-nolisting
override to stop generation of listing file, disables -listing option
-debuglisting
generate listing of debug info into codfile listing
-debugtest
read in generated .debug file and regenerate into .debug2
(these two files should be identical)
-import=<file>.jar[;...]
list dependent jar files
-defs=<file>.def[;...]
list rapc .def files
-classpath=<path>[;...]
list locations of classes that javac will want
-class=<classname>
specify name of class containing main entry point
-jad=<file>jad
read file for application information. i.e. version, name, url, etc...
-noimport
the resulting library may not be linked against
-midlet
the resulting codfile is a midlet, generate a preverified .jar file too
-exepath=<path>
where to find preverify.exe when building midlets
-eviscerate
-noeviscerate
.class files that are collected into output .jar file have the
code attributes removed. all methods are marked as native.
eviscerate is default when building library, otherwise noeviscerate.
-convertpng
when generating image data as a binary resource, do convert the
image file format to PNG
-noshortname
when generating binary resources, only use the long name
-noparsecod
when reading import .jar files, don't try to parse any .cod files, only
read the .class files for import information
-nodebug
suppress generation of .debug output file (and debug attributes in .class files)
has side effect of setting timestamp to fixed value
-debugclass
enable generation of debug attributes in .class files, overrides nodebug
-notarget13
don't pass "-target 1.3" to javac, "-target 1.3" tells JDK 1.4 javac
to behave more like JDK 1.3, this works better for us
-target11
do pass "-target 1.1" to javac, this is how we used to do things
-javacompiler=<toolname>
use <toolname> as the java compiler. default is javac.
jikes and wjava have been tested.
-nolimit
don't abort compilation if a sibling .cod file gets too big.
-noloadtool
when running the JDK javac or jar programs, don't try to load them into
the current VM, execute them as external processes
-deprecation
pass -deprecation switch to java compiler
-nowarn
pass -nowarn switch to the java compiler
also suppress warnings generated by rapc
-quiet
only display errors
-warning
generate warning messages
-nopackagewarning
suppress warning about package name not matching directory structure
-verbose
dump information about what is going on, leave temp files lying around
-traceback
if an error occurs, dump the java execption trackback
-timing
display information about time taken for various phases of compilation
-exclusive
try to leave unreferenced components out of library cod files.
Will break published API.
-nativesonly
don't produce codfile output, only XXXnatives.h and XXXnativeType.h output files
-optimizepackage
suppress package protection symbolic information. may reduce codfile size.
does not allow packages to straddle two cod files.
-preverified
use the sun preverifier stackmap information in the generate codfile
-nooptimize[={nop,arrayinit,deadcode,checkcast,trivial,jump,accessor,mutator,pushpop,useless_case}]
suppress optimizations
-define=<label>[;...]
run javapp style pre-processor with <label> defined
-sibling
generate sibling .cod files, this is now the default behaviour
-target=3.2
don't generate sibling .cod files, do things the old way
-target=lastrel
generate codefiles the previous release can use as well as headrev.
This option is used ease the pain of developing on headrev and the current release.
Anything further back get install or sync to the branch.
-wx
treat some warnings as errors
-nowx=a.b.c.X[;...]
don't treat warnings as errors for class a.b.c.X
-snapshot
when building from .java source files create a <name>-snapshot.jar
file containing the not preverified and not eviscerated .class files
-t0
set timestamp to a fixed value instead of the current seconds
-warnkey=0xabcdef99[;...]
generate a warning if specified key needs to be added to the .csl file
-workspace=<filename>
adds the <filename> to the .debug output file so the IDE can do better
browsing
-tmpdir=<directory>
use the specified directory for temporary file storage (perhaps a ramdisk)
-codefull=NNNN
use NNNN as the limit to how much code goes into a sibling .cod file
before a new sibling is started, default 63488
-datafull=NNNN
use NNNN as the limit to how much data goes into a sibling .cod file
before a new sibling is started, default 61440
-vtablefull=NNNN
use NNNN as the limit to how large the vtable estimate for the
classes in a sibling .cod file can get before a new sibling is
started, default 61440
-fieldfull=NNNN
use NNNN as the limit to how how many fields a class can add
to a sibling .cod file before a new sibling is started, default 61440
-slicesize=NNNN
use NNNN as the slice size associated with a large binary resource file
default is 8192, a large binary resource file is larger than 60k
-languages=<iso-code>[;<iso-code>...]
mark the .cod file as providing translated language resources for <iso-code>
-noverifyerr
set the "IsNoVerifyErr" flag in the codfile to enforce no verify errors when linking.
Some errors like missing methods are otherwise allowed that will be detected at runtime.
-iconsize=NNNN
use NNNN as the maximum allowed size for icon data, default is 16384
-resourcesize=NNNNN
use NNNN as the max binary resource size allowed before the file is sliced up
default is 61440
-sourceroot=<directory>[;...]
use list of directories to find .java source file names for debug info
-emitstatic=<prefix>
generate a file with all the static final constant values for every
class whos fullname (including package) starts with prefix
this is useful for some of the TCK signature tests that assume we
have such data on the device
-cr
compress resource files
-rtninfo
generate .wts output files with info for the OTASL upgrade process to
be able to estimate how much flash will be required by the destination
bundle for VM data structures
-noexport
do not output an export.xml file when generating a library cod file
-nojar
do not output a .jar file
-alias=<name>
add an alias <name> to a codfile i.e. rapc -codename=<codfile> -alias=<name>
The following options generally appear in the .def files:
-natives=<method-prototype>[;...]
native methods
-exports=<methodprototype>[;...]
methods called from native code
-diagnoseexports=<method-prototype>[;...]
methods that are diagnosed, as a warning, if missing
-statics=<member-name>[;...]
static data that is accessed from native code
-virtual methods=<method-prototype>[;...]
virtual methods that are called from native code
-interface methods=<method-prototype>[;...]
interface methods that are called from native code
-fields=<member-name>[;...]
instance data that is accessed from native code
-strings=<member-name>[;...]
literal data that is accessed from native code
-persistable=<interfacename>[;...]
interfaces that are implicitly persistable, the first
entry in this list is the marker interface
-classes=<classname>[;...]
classes that are accessed from native code
-interfaces=<interfacename>[;...]
interfaces that are accessed from native code
-rootclassvtable=<method-prototype>[;...]
methods that appear in the vtable for java.lang.Object
Use one of -codename or -library, if both are specified, -library wins.
Don't use -eviscerate when making a midlet, or the output .jar file won't be
a valid midlet.
For options that have '=', the leading '-' is optional.
Options may appear on the command line or in the .def file.
In the .def file the option name is on a single line, enclosed in '[' and ']'.
the option values appear individually on subsequent lines.
*/
package net.rim.tumbler.rapc;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import net.rim.tumbler.ExecUtil;
import net.rim.tumbler.OSUtils;
import net.rim.tumbler.config.WidgetConfig;
import net.rim.tumbler.file.FileManager;
import net.rim.tumbler.session.BBWPProperties;
import net.rim.tumbler.session.SessionManager;
public class Rapc {
// Output file extensions
public static String[] EXTENSIONS = new String[] { ".cod", ".jar", ".csl", ".cso", ".rapc", ".jar" };
private static final String FILE_SEP = System.getProperty( "file.separator" );
private static final String NL = System.getProperty( "line.separator" );
private static final String SOURCE_FILE = "sourcefile";
// RAPC Collections
private List< String > _imports = new ArrayList< String >();
private List< String > _inputFiles = new ArrayList< String >();
// Rapc single arguments
// Class name that contains the main entry point
private String _className;
private String _codeName;
private String _additional;
private String _javac;
// Paths
private String _cwd;
private String _bin;
private List<String> _tmpFilesToDelete;
// Data
private Map< String, String > _env;
private WidgetConfig _widgetConfig;
// / <summary>
// / Default constructor
// /
// / Constructor uses predetermined defaults
// / </summary>
public Rapc( BBWPProperties bbwpProperties, WidgetConfig widgetConfig, List< String > compiledJARDeps ) {
_widgetConfig = widgetConfig;
_bin = bbwpProperties.getRapc();
_imports = bbwpProperties.getImports();
// If there are additional compiled JARs that the extensions might
// depend on, make sure they get added to classpath
if( _imports != null && compiledJARDeps != null ) {
for( String path : compiledJARDeps ) {
_imports.add( path );
}
}
_javac = bbwpProperties.getJavac();
_cwd = SessionManager.getInstance().getSourceFolder();
_codeName = SessionManager.getInstance().getArchiveName();
_additional = SessionManager.getInstance().isVerbose() ? "-warning" : bbwpProperties.getAdditional() + " -nowarn";
if( !OSUtils.isWindows() ) {
_bin = OSUtils.replaceWithForwardSlash( _bin );
_javac = OSUtils.replaceWithForwardSlash( _javac );
_tmpFilesToDelete = new ArrayList< String >();
}
// Set system environment variables
_env = new HashMap< String, String >();
for( Map.Entry< String, String > val : System.getenv().entrySet() ) {
_env.put( val.getKey().toUpperCase(), val.getValue() );
}
// A patch to force rapc.exe to search for rapc.jar first in the current
// directory
_env.put( "PATH",
System.getenv( "PATH" ) + ";" + System.getProperty( "user.dir" ) + ";"
+ getJavaBin( bbwpProperties.getJavaHome() ) );
}
// / <summary>
// / Generate the parameters to pass to rapc
// / </summary>
// / <returns>A String of passable parameters</returns>
public String generateParameters() {
// forcing the -noshortname switch for rapc
// http://hhappsweb/hhwiki/Browser/BrowserField2#Display_a_Web_Page_from_a_resource_within_your_COD_file
StringBuilder param = new StringBuilder( "-noshortname " );
if( _className != null && !_className.trim().isEmpty() ) {
param.append( "-class \"" + _className + "\" " );
}
if( _codeName != null && !_codeName.trim().isEmpty() ) {
param.append( "-codename=\"" + _codeName + "\" " );
}
if( _imports.size() > 0 ) {
param.append( "-import=\"" );
String anImport;
for( int i = 0; i < _imports.size(); ++i ) {
anImport = _imports.get( i );
// copy any JAR (including net_rim_api.jar) to safe source folder so RAPC does not have to deal with spaces
if( needsCopyToTmp( anImport ) ) {
anImport = copyFilesToTmp( anImport, SessionManager.getInstance().getSourceFolder() );
}
param.append( anImport );
if( i < _imports.size() - 1 && _imports.size() != 1 ) {
param.append( File.pathSeparatorChar );
}
}
param.append( "\" " );
}
if( _javac != null && !_javac.trim().isEmpty() ) {
param.append( "-javacompiler=" );
param.append( _javac );
param.append( " " );
}
if( _additional != null && !_additional.trim().isEmpty() ) {
param.append( _additional + " " );
}
int idx = _bin.lastIndexOf( File.separator );
String binDir = ( idx < 0 ) ? _bin : _bin.substring( 0, idx );
// copy preverify executable to safe source folder so RAPC does not have to deal with spaces
if( needsCopyToTmp( binDir ) ) {
String tmp = copyFilesToTmp( binDir + FILE_SEP + "preverify", SessionManager.getInstance().getSourceFolder() );
binDir = new File( tmp ).getParentFile().getAbsolutePath();
}
String exepath = "-exepath=\"" + binDir + "\" ";
param.append( exepath );
param.append( "@" + SOURCE_FILE );
return param.toString().trim();
}
// rapc is not happy if there are too many files listed on command line, the solution
// is to generate a file that contains all source files
// http://download.oracle.com/javase/1.4.2/docs/tooldocs/windows/javac.html#commandlineargfile
private void generateSourceFile() throws IOException {
File dest = new File( this._cwd + File.separator + SOURCE_FILE );
BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( dest ), "UTF8" ) );
for( String file : _inputFiles ) {
writer.write( '\"' );
writer.write( file );
writer.write( '\"' );
writer.write( '\n' );
}
writer.close();
}
// / <summary>
// / The input files:
// / .java: A Java source program file that javac must compile.
// / .class: A Java .class file that javac has compiled.
// / .jar: An archive of files that you need to include in the compilation
// set.
// / .jad: An input file that contains application information. For example,
// it contains a list of attributes that the MIDP specification specifies.
// / </summary>
public List< String > getInputFiles() {
return _inputFiles;
}
// / <summary>
// / Specify the name and location of the output .cod file;
// / typically the output .cod file uses the same name of the .jar file.
// /
// / = <path>\[...]]<filename>
// / </summary>
public String getCodeName() {
return _codeName;
}
// / <summary>
// / The name of the class that contains the main entry point of the
// application; without this option, RAPC uses the first main(String[])
// method it finds as the entry point.
// /
// / = <classname>
// / </summary>
public String getMainEntryPointClassFile() {
return _className;
}
// / <summary>
// / -library
// / Specify the name and location of the output .cod file as a library.
// /
// / = [<path>\[...]]<filename>
// / </summary>
// / <summary>
// / -quiet
// / Display errors only
// / </summary>
// / <summary>
// / -verbose
// / Display information about RAPC activity. RAPC stores this
// / information in intermediate and temporary files in the
// / user's temporary folder. RAPC does not delete the temporary
// / files.
// / </summary>
// / <summary>
// / List dependent .jar files; for example list RIM APIs and other
// dependent libraries.
// /
// / = <file>.jar[;...]
// / </summary>
public List< String > getImports() {
return _imports;
}
// / <summary>
// / Any additional non supported rapc arguments to pass
// / to the executable
// / </summary>
public String getAdditionalParameters() {
return _additional;
}
// / <summary>
// / Java compiler supported rapc arguments to pass
// / to the executable
// / </summary>
public String getJavac() {
return _javac;
}
// / <summary>
// / Returns folder where javac.exe and java.exe files are located
// / based on entry in bbwp property file.
// / </summary>
private String getJavaBin( String javaHome ) {
if( javaHome != null && !javaHome.trim().isEmpty() ) {
return javaHome + File.separator + "bin";
}
return "";
}
// / <summary>
// / Runs process with generated parameters
// / </summary>
// / <returns>if succeeds: true, otherwise false</returns>
public boolean run( List< String > inputFiles ) throws Exception {
_inputFiles = inputFiles;
_inputFiles.add( generateRapcFile() );
generateSourceFile();
return run( generateParameters() );
}
// / <summary>
// / Runs process with specified parameters
// / </summary>
// / <returns>if succeeds: true, otherwise false</returns>
private boolean run( String parameters ) {
Iterator< Map.Entry< String, String >> iterator = _env.entrySet().iterator();
List< String > envList = new ArrayList< String >( _env.size() );
while( iterator.hasNext() ) {
Map.Entry< String, String > entry = iterator.next();
// Doesn't throw an exception.
// But we want our own variables to take priority over preset ones
// So remove duplicates
envList.add( entry.getKey() + "=" + entry.getValue() );
}
String cmdStr;
if( OSUtils.isWindows() ) {
cmdStr = "\"" + _bin + "\" " + parameters;
} else {
cmdStr = prepareCmdStringOnMac( parameters );
}
Process proc = ExecUtil.exec( this._cwd, cmdStr, envList.toArray( new String[ envList.size() ] ) );
removeTmpFiles();
return ( proc.exitValue() == 0 );
}
private String prepareCmdStringOnMac( String parameters ) {
String cmdStr;
if( _bin.indexOf( ".jar" ) >= 0 ) {
// copy rapc.jar to safe source folder so we do not have to deal with spaces
if( needsCopyToTmp( _bin ) ) {
_bin = copyFilesToTmp( _bin, SessionManager.getInstance().getSourceFolder() );
}
parameters = parameters.replaceAll( "\"", "" );
cmdStr = "java -jar " + _bin + " " + parameters;
} else {
cmdStr = _bin + " " + parameters;
}
return cmdStr;
}
private boolean needsCopyToTmp( String path ) {
return ( !OSUtils.isWindows() && path.contains( " " ) );
}
private void removeTmpFiles() {
if( _tmpFilesToDelete != null ) {
for( String file : _tmpFilesToDelete ) {
new File( file ).delete();
}
}
}
private String copyFilesToTmp( String fromPath, String toDir ) {
File src = new File( fromPath );
if( src.exists() ) {
File dest = new File( toDir, src.getName() );
// Use native copy to preserve permissions of executable files
Process proc = ExecUtil.exec( null, new String[] { "cp", src.getAbsolutePath(), toDir }, null );
if( proc.exitValue() == 0 ) {
_tmpFilesToDelete.add( dest.getAbsolutePath() );
return dest.getAbsolutePath();
}
}
return null;
}
// / <summary>
// / The current working directory for the executables to run in
// / </summary>
public void setCurrentWorkingDirectory( String cwd ) {
this._cwd = cwd;
}
public String getCurrentWorkingDirectory() {
return this._cwd;
}
// / <summary>
// / Environment variables to set when executing the Preverify and RAPC
// binaries
// / </summary>
public void setEnvironmentVariables( Map< String, String > env ) {
this._env = env;
}
public Map< String, String > getEnvironmentVariables() {
return this._env;
}
// Generate our .rapc file
private String generateRapcFile() throws IOException {
String fileName = _cwd + FILE_SEP + SessionManager.getInstance().getArchiveName() + ".rapc";
// BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
// This ensures that the generated RAPC file is UTF8 encoded
BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( fileName ), "UTF8" ) );
writer.write( "MIDlet-Name: " + _widgetConfig.getName() + NL );
if( _widgetConfig.getDescription() != null ) {
writer.write( "MIDlet-Description: " + _widgetConfig.getDescription() + NL );
}
writer.write( "MIDlet-Version: " + _widgetConfig.getVersion() + NL );
writer.write( "MIDlet-Vendor:" );
if( _widgetConfig.getAuthor() != null && _widgetConfig.getAuthor().length() != 0 ) {
writer.write( " " + _widgetConfig.getAuthor() );
} else {
writer.write( " Unknown" );
}
writer.write( NL );
writer.write( "MIDlet-Jar-URL: " + SessionManager.getInstance().getArchiveName() + ".jar" + NL );
writer.write( "MIDlet-Jar-Size: 0" + NL );
writer.write( "MicroEdition-Profile: MIDP-2.0" + NL );
writer.write( "MicroEdition-Configuration: CLDC-1.1" + NL );
Vector< String > icons = _widgetConfig.getIconSrc();
String icon = ( icons == null || icons.isEmpty() ) ? "" : icons.elementAt( 0 );
writer.write( "MIDlet-1: " + _widgetConfig.getName() + "," + icon + ",rim:foreground;WIDGET;" );
if( SessionManager.getInstance().debugMode() ) {
writer.write( "DEBUG_ENABLED" );
}
writer.write( NL );
// If app to run on startup, set title and icon to appear
if( _widgetConfig.getBackgroundSource() != null && _widgetConfig.isStartupEnabled() ) {
writer.write( "MIDlet-2: " + _widgetConfig.getName() + "," + icon + ",rim:runOnStartup;WIDGET;" + NL );
}
// The rest icons
int iconCount = 0;
if( icons != null ) {
for( int i = 1; i < icons.size(); i++ ) {
iconCount = iconCount + 1;
writer.write( "RIM-MIDlet-Icon-1-" + iconCount + ": " + icons.elementAt( i ) + NL );
}
}
// / Hover icons are not displayed correctly for application if
// / used as icons
// / FIX - make copies of all hover icons
Vector< String > hoverIcons = _widgetConfig.getHoverIconSrc();
if( hoverIcons != null ) {
for( int i = 0; i < hoverIcons.size(); i++ ) {
iconCount = iconCount + 1;
writer.write( "RIM-MIDlet-Icon-1-" + iconCount + ": "
+ copyIcon( hoverIcons.elementAt( i ), SessionManager.getInstance().getSourceFolder() ) + ",focused" + NL );
}
}
if( iconCount > 0 ) {
writer.write( "RIM-MIDlet-Icon-Count-1: " + iconCount + NL );
}
/* Repeat the same for the run on startup instance of the icon */
// Generate icon for RIM-MIDlet-2 (startup entry point)
iconCount = 0;
if( icons != null ) {
for( int i = 1; i < icons.size(); i++ ) {
iconCount = iconCount + 1;
writer.write( "RIM-MIDlet-Icon-2-" + iconCount + ": " + icons.elementAt( i ) + NL );
}
}
// Generate hover-icons for RIM-MIDlet-2 (startup entry point)
// Hover icons are not displayed correctly for application if
// used as icons. Since above code already made copies, return
// the same icon copy generated above
hoverIcons = _widgetConfig.getHoverIconSrc();
if( hoverIcons != null ) {
for( int i = 0; i < hoverIcons.size(); i++ ) {
iconCount = iconCount + 1;
writer.write( "RIM-MIDlet-Icon-2-" + iconCount + ": "
+ copyIcon( hoverIcons.elementAt( i ), SessionManager.getInstance().getSourceFolder() ) + ",focused" + NL );
}
}
// Generate count for RIM-MIDLlet-2 (startup entry point) icon
if( iconCount > 0 ) {
writer.write( "RIM-MIDlet-Icon-Count-2: " + iconCount + NL );
}
// TODO:CLEAN UP THIS LOGIC ELSEWHERE
if( _widgetConfig.getForegroundSource().length() == 0 ) {
writer.write( "RIM-MIDlet-Flags-1: 2" + NL );
} else {
writer.write( "RIM-MIDlet-Flags-1: 0" + NL );
}
// Alternate Entry point
if( _widgetConfig.getBackgroundSource() != null && _widgetConfig.isStartupEnabled() ) {
writer.write( "RIM-MIDlet-Flags-2: 3" + NL );
}
writer.close();
return fileName;
}
/*
* Make a copy of an icon in a specified directory. If that icon exists in the specified directory a copy is generated and
* returned. If a copy of the icon already exists, the copy is returned. If the icon doesn not exist in the directory then the
* icon is returned.
*/
private String copyIcon( String icon, String directory ) {
String tempPrefix = "____HOVER_ICON_";
// Copy the file with new prefix
File from = new File( directory + FILE_SEP + icon );
File to = new File( from.getParent() + FILE_SEP + tempPrefix + from.getName() );
if( ( !to.exists() ) && from.exists() ) {
// Icon exists in directory but icon copy does not
try {
FileManager.copyFile( from, to );
// Return copied icon file name
return to.getAbsolutePath().substring( directory.length() + 1 );
} catch( Exception e ) {
return icon;
}
} else if( to.exists() ) {
// Return initial copy
return tempPrefix + icon;
}
// Icon not present in the specified directory
return icon;
}
}