/* * 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; } }