#!@PERL@ -w # # (c) 1998-2004 Jan-Henrik Haukeland, # =head1 NAME mmake - generate a Java Makefile =head1 SYNOPSIS mmake [ -d | -v ] =head1 DESCRIPTION This program will generate a Makefile for Java source files. Use the I<-d> option to accept all defaults. After running mmake, you will obtain a Makefile in the directory from where you started the program. The Makefile will handle java files in the current directory and in any sub-directories. Use the generated Makefile with mmake as follows: To compile Java files just type B. It's also possible to run make with one of the following targets: I Where 'make doc' runs javadoc on the source files, it will only work for files in a package. The command 'make clean' removes class files and other temporary files. The command 'make jar' creates a jar file with all class files (and other files of your choice, see the JAR_OBJS variable in the Makefile). The command 'make srcjar' creates a jar file with all java files. The command 'make bundle' creates a Mac OS X Application Bundle with all the jar file. The command 'make install' will install a jar file, app bundle, class files and any shell wrappers you have made. (A shell script must have the extension .sh to be installed). Use 'make uninstall' to remove installed files. The command 'make help', shows a help text with available targets. The command 'make tags' will generate a tag file for Emacs. And finally the command 'make depend' creates a dependency graph for the class files. (The dependency graph will be put in a file called I, which is included in the Makefile) You don't have to run mmake each time you add a new java file to your project. You can add as many new java files as you like, the Makefile will find them. This is the case as long as you don't add a new package. In that case, you must either run mmake again or update the PACKAGE variable in the Makefile. This is because the Makefile uses this variable to find directories with java files. The program mmake is able to create a dependency graph for your java files. To do this, it needs the I compiler from IBM. Get jikes from B.You would probably be more content with jikes anyhow, since it is much faster than javac. To create a dependencies graph, do a I before running I. =head1 A NOTE ON INSTALLATION The Makefile created with mmake will do a fair job installing the different files that makes up your system. It uses the following Makefile variables when it conducts the install routine: =over 4 =item * PREFIX =item * CLASS_DIR =item * JAR_DIR =item * BUNDLE_DIR =item * DOC_DIR =item * SCRIPT_DIR =back =head2 PREFIX This variable will be prepended to all other directory variables above. It is used for grouping the other directories into one root directory. If you don't want that, you may simply set the variable to an empty string in the Makefile. If the variable is empty you could still use it on the command line when you run make, for instance for a one-shoot installation like: B =head2 CLASS_DIR This variable denotes the top directory from where all class files will be installed. Its default value is B, which I believe is a good value. B If you I want to install any class files (because you are, for example, only going to use a jar file), set this variable to an empty string and no class files will be installed. Resource files will also be installed below this directory if such files are present in a package structure. This is useful if you are using e.g. ResourceBundles to Localize your application and have your property files in it's own directory in the package structure. =head2 JAR_DIR This variable tells the Makefile where to install the jar file. The default value is B, which is also a good default value. =head2 BUNDLE_DIR This variable tells the Makefile where to install the app bundle. The default value is B, which is also a good default value. =head2 DOC_DIR When you run javadoc, all the html files will be put into this directory. Its default value is B. You should probably keep that name, but then again, you may change it as you like. =head2 SCRIPT_DIR The Makefile uses this variable to install any shell wrapper-scripts that you have created. If you write an application, it is always nice for the user that you provide a wrapper script to start the application. Its default value is B. (The Makefile will only install shell-scripts that has the extension .sh. The mmake script will tell the Makefile where to look for shell-scripts) =head2 INSTALLATION SUMMARY If you keep the default values you will get an installation tree that looks like this: `-- PREFIX |-- bin |-- classes | `-- package <--- Example of a sub-directory | |-- sub-package1 | |-- sub-package2 | `-- sub-package3 |-- doc | `-- api-docs `-- lib =head1 USING THE C-PREPROCESSOR This is a excellent tool for managing projects with several different versions. The idea behind using the C preprocessor with Java is to better manage different versions more easily. This is done by using CPP conditional statements in the source files. I would strongly advise you not to use CPP to redefine the Java language itself. To use the C preprocessor together with Java, you can change the name of the source files that you want to preprocess -- from .java to .xjava. The Makefile has a rule to build .class files from .xjava files. It is B necesarry to change every file from .java to .xjava. The Makefile will work well and consistently in an environment of both .java and .xjava files. (E.g. 'make clean' will only remove .java files that were created from a .xjava file. Other java files will, of course, I be removed.) You can now use cpp Conditionals in Your Java-code, for example, as follows: #ifdef JAVA1_1 [code1] #else [code2] #endif The JAVA1_1 label in the above example is tested against the VERSION variable in the Makefile. That is, if the VERSION variable is JAVA1_1, then [code1] would be compiled and [code2] left out. Likewise, if VERSION is something else than JAVA1_1, then [code2] would be compiled and [code1] left out of the resulting .class file. =head1 NOTES mmake will give you I Makefile for managing your Java files. Although it's easy to setup and use mmake in a recursive makefile context, you don't want to do that. To see why, read the excellent article: B at I =head1 DEPENDENCIES mmake will need the following: =over 4 =item * Perl 5.x =item * Gnu make =item * Gnu xargs (recommended) =back =head1 AUTHOR Jan-Henrik Haukeland =cut use strict; use vars qw($opt_d $opt_v); use Getopt::Std; require 5.000; # Need this perl version at least # Prototypes sub getline($$); sub getdirline($$); sub getpreviewline($$$); sub do_get(); sub do_find($$); my $REVISION= sprintf("%d.%02d", q$Revision: 1.13 $ =~ /(\d+)\.(\d+)/); my $VERSION= "2.3"; # The program version (my $PROG = $0)=~ s,.*/,,; # This Program name (usually 'mmake') my $M= "Makefile"; # The Java Makefile my @packages= (); # Array holding packages, i.e. subdirectories my @all_packages= (); my @scripts= (); # Array holding shell-script directories my @resources= (); # Array holding resource files my $toplevel= ""; # Defined if toplevel java files # Parse command line options getopts("dv") || die "Usage: $PROG [ -d | -v ]\n"; if ( defined $opt_v ) { print "This is mmake, version $VERSION\n"; exit(0); } # --------------------- # Assign macro defaults # --------------------- my $javac= "@JAVAC@"; my $jflags= ""; my $javadoc= "javadoc"; my $jdocflags= "-version -author"; my $jar= "jar"; my $jarflags= "cvf0"; my $jarfile= ""; my $appfile= ""; my $prefixdir= ""; my $docdir= "doc/api-docs"; my $jardir= "lib"; my $appdir= "lib"; my $classdir= "classes"; my $bindir= "bin"; my $cpp= "cpp"; my $cppflags= "-C -P"; if (-t and ( !$opt_d )) { # Let the user override the defaults print "Give Makefile variables or just type [enter] for default\n\n"; $javac= getline("JAVAC", $javac); $jflags= getline("JAVAC flags", $jflags); $javadoc= getline("JAVADOC", $javadoc); $jdocflags= getline("JAVADOC flags", $jdocflags); $jar= getline("JAR", $jar); $jarflags= getline("JAR flags", $jarflags); $jarfile= getline("JAR File name (e.g. foobar.jar)", $jarfile); $appfile= getline("APP Bundle name (e.g. foobar.app)", $appfile); $prefixdir= getdirline("PREFIX dir. (Will be prepended to other ". "install dir)", $prefixdir); $docdir= getpreviewline("INSTALL dir. for javadoc html-files.", "$prefixdir$docdir", $docdir); $classdir= getpreviewline("INSTALL dir. for class files", "$prefixdir$classdir", $classdir); $bindir= getpreviewline("INSTALL dir. for shell-scripts", "$prefixdir$bindir", $bindir); $jardir= getpreviewline("INSTALL dir. for the jar file ", "$prefixdir$jardir", $jardir); $appdir= getpreviewline("INSTALL dir. for the app bundle ", "$prefixdir$appdir", $appdir); if( getline("Use CPP [y|n] ?", "no") =~ /^y/i ) { $cpp= getline("CPP", $cpp); $cppflags= getline("CPP flags", $cppflags); } print "\n"; } # Locate the java files/packages do_get(); die "No java source files found\n" unless @packages or $toplevel; # If an old Makefile exists, rename it if (-f $M) { rename($M, "$M.old") or die "$PROG: Cannot rename local Makefile.\n"; } # Then create the new makefile open(MAKEFILE, ">$M") || die "$PROG: Cannot create '$M': $!\n"; print MAKEFILE < [Installing jar file, \$(JAR_FILE) in \$(JAR_DIR)] " \$(INSTALL_DIR) \$(JAR_DIR) \$(check-exit) \$(INSTALL_FILE) \$(JAR_FILE) \$(JAR_DIR) \$(check-exit) uninstall:: \@echo "===> [Removing jar file, \$(JAR_FILE) from \$(JAR_DIR)] " \$(RM) \$(JAR_DIR)/\$(JAR_FILE) \$(check-exit) else install:: \@echo "No jar install dir defined" endif clean:: \$(RM) \$(JAR_FILE) else jar: \@echo "No jar file defined" endif SRC_JAR_FILE := \$(basename \$(JAR_FILE))-src\$(suffix \$JAR_FILE) # Source jar target srcjar : \$(SRC_JAR_FILE) \$(SRC_JAR_FILE): \$(JAVA_SRC) \$(RESOURCE_OBJS) \$(FIND) \$(TOPLEVEL) \$(JAR_OBJS: .class=.java) -print | \$(XARGS) \\ \$(JAR) \$(JAR_FLAGS) \$\@ # Bundle target ifneq (\$(strip \$(BUNDLE_FILE)),) bundle: \$(BUNDLE_FILE) \$(BUNDLE_FILE) : \$(JAR_FILE) \$(INSTALL_DIR) \$(BUNDLE_FILE)/Contents/Resources/Java \$(check-exit) \$(INSTALL_DIR) \$(BUNDLE_FILE)/Contents/MacOS \$(check-exit) \$(INSTALL_PROG) \$(JAVA_STUB) \$(BUNDLE_FILE)/Contents/MacOS/ \\ \$(check-exit) ( \$(CAT) \$(BUNDLE_RESOURCE_DIR)/Info.plist | \$(SED) -e \\ s/VERSION/\$(VERSION)/ >98762infoplist876 ) \$(check-exit) \$(INSTALL_FILE) 98762infoplist876 \\ \$(BUNDLE_FILE)/Contents/Info.plist \$(check-exit) \$(RM) 98762infoplist876 \$(check-exit) \$(INSTALL_FILE) \$(JAR_FILE) \$(BUNDLE_FILE)/Contents/Resources/Java checkexit="";for f in \$(BUNDLE_RESOURCES); do \\ \$(INSTALL_FILE) \$(BUNDLE_RESOURCE_DIR)\$\$f \$(BUNDLE_FILE)/Contents/Resources/ \\ || checkexit=\$?; \\ done; test -z \$\$checkexit ifneq (\$(strip \$(BUNDLE_DIR)),) # This is probably bad, but I don't know how else to do it install:: \$(BUNDLE_FILE) \@echo "===> [Installing app bundle, \$(BUNDLE_FILE) in \$(BUNDLE_DIR)] " \$(INSTALL_DIR) \$(BUNDLE_DIR) \$(check-exit) \$(CP) -R \$(BUNDLE_FILE) \$(BUNDLE_DIR) \$(check-exit) \$(INSTALL_FILE) \$(BUNDLE_FILE) \$(BUNDLE_DIR) \$(check-exit) uninstall:: \@echo "===> [Removing bundle file, \$(BUNDLE_FILE) from \$(BUNDLE_DIR)] " \$(RM) -r \$(BUNDLE_DIR)/\$(BUNDLE_FILE) \$(check-exit) else install:: \@echo "No bundle install dir defined" endif clean:: \$(RM) -r \$(BUNDLE_FILE) else bundle: \@echo "No bundle file defined" endif # Install target for Classes and Resources ifneq (\$(strip \$(CLASS_DIR)),) install:: \$(JAVA_OBJS) \@echo "===> [Installing classes in \$(CLASS_DIR)] " \$(INSTALL_DIR) \$(CLASS_DIR) \$(check-exit) \$(foreach dir, \$(JAVA_DIRS) \$(RESOURCE_DIRS), \\ \$(INSTALL_DIR) \$(CLASS_DIR)/\$(dir) \$(check-exit)) \$(foreach file, \$(INSTALL_OBJS), \\ \$(INSTALL_FILE) \$(file) \$(CLASS_DIR)/\$(file) \\ \$(check-exit)) uninstall:: \@echo "===> [Removing class-files from \$(CLASS_DIR)] " \$(foreach file, \$(INSTALL_OBJS), \\ \$(RM) \$(CLASS_DIR)/\$(file) \\ \$(check-exit)) else # Print a warning here if you like. (No class install dir defined) endif # Depend target ifeq (\$(findstring jikes,\$(JAVAC)),jikes) depend: \$(XJAVA_OBJS) \$(DEPEND_OBJS) ( \$(CAT) \$(DEPEND_OBJS) | \$(SED) -e '/\\.class\$\$/d' \\ -e '/.*\$\$.*/d' > \$(MAKEFILE_DEPEND); \$(RM) \$(DEPEND_OBJS); ) else depend: \@echo "mmake needs the jikes compiler to build class dependencies" endif # Doc target ifneq (\$(strip \$(JAVADOC_PACKAGES)),) doc: \$(JAVA_SRC) \@echo "===> [Installing java documentation in \$(DOC_DIR)] " \$(INSTALL_DIR) \$(DOC_DIR) \$(check-exit) \$(JAVADOC) \$(JAVADOC_FLAGS) -d \$(DOC_DIR) \$(JAVADOC_PACKAGES) else doc: \@echo "You must put your source files in a package to run make doc" endif # Script target ifneq (\$(strip \$(SCRIPT_OBJS)),) all:: \$(SCRIPT_OBJS) ifneq (\$(strip \$(SCRIPT_DIR)),) install:: \$(SCRIPT_OBJS) \@echo "===> [Installing shell-scripts in \$(SCRIPT_DIR)] " \$(INSTALL_DIR) \$(SCRIPT_DIR) \$(check-exit) \$(foreach file, \$(SCRIPT_OBJS), \\ \$(INSTALL_PROG) \$(file) \$(SCRIPT_DIR) \$(check-exit)) uninstall:: \@echo "===> [Removing shell-scripts from \$(SCRIPT_DIR)] " \$(foreach file, \$(SCRIPT_OBJS), \\ \$(RM) \$(SCRIPT_DIR)/\$(file) \$(check-exit)) else # Print a warning here if you like. (No script install dir defined) endif clean:: rm -f \$(SCRIPT_OBJS) endif # Tag target tags: \@echo "Tagging" \$(ETAGS) \$(filter-out \$(XJAVA_OBJS), \$(JAVA_SRC)) \$(XJAVA_SRC) # Various cleanup routines clean:: \$(FIND) . \\( -name '*~' -o -name '*.class' \\) -print | \\ \$(XARGS) \$(RM) \$(FIND) . -name '*.u' -print | \$(XARGS) \$(RM) ifneq (\$(strip \$(XJAVA_SRC)),) clean:: \$(RM) \$(XJAVA_OBJS) endif # ---------------------------------------- # Include the dependency graph if it exist # ---------------------------------------- MAKEFILE_DEPEND = makefile.dep DEPEND = \$(wildcard \$(MAKEFILE_DEPEND)) ifneq (\$(DEPEND),) include \$(MAKEFILE_DEPEND) endif #package targets EOT # print package targets foreach my $package (@all_packages, @packages) { print MAKEFILE "$package : \$(call PACKAGE_OBJS,$package)\n"; } print MAKEFILE "\n"; close(MAKEFILE); print "'$M' created\n"; exit(0); # ------------------------------------------------------------------------- # # ----------------------------- Subroutines ------------------------------- # # ------------------------------------------------------------------------- # sub getline($$) { my $key= shift; my $dir= shift; my $j; print "$key [$dir]: "; chomp($j= ); return ($j =~ /\w/ ? $j : $dir); } sub getdirline($$) { my $key= shift; my $dir= shift; my $j; print "$key [$dir]: "; chomp($j= ); if ( $j =~ /(\w)|(^\.)/ ) { $j .="/" if ( substr($j, -1) ne "/" ); return $j; } return $dir; } sub getpreviewline($$$) { my $key= shift; my $preview= shift; my $dir= shift; my $j; print "$key [$preview]: "; chomp($j= ); return ($j =~ /\w/ ? $j : $dir); } sub do_get() { my $cmd; # Get directories with java files $cmd= q$find . \( -name "*.java" -o -name "*.xjava" \) $ .q$-print 2>/dev/null |$; @packages= do_find("p", $cmd); return unless @packages or $toplevel; # Get directories with shell-script files $cmd= q$find . \( -name "*.sh" \) -print 2>/dev/null |$; @scripts= do_find("s", $cmd); # Get directories with *only* resource files $cmd= q$find . \( -name "*.properties" -o -name "*.gif" -o -name $ .q$"*.au" \) -print 2>/dev/null |$; # A hash with package values my %p= map { ($_), $_ } @packages; my %ap=map { ($_), $_ } (map {get_parents($_);} @packages); # Set only elements located in a package and not # already in the packages array @resources= grep { ! $p{$_} && $_ =~ /\./ } do_find("p", $cmd); @all_packages= grep { ! $p{$_} } keys(%ap); } sub do_find($$) { my $mode= shift; # p = use package syntax my $cmd= shift; my %unique= (); open(MYDIR, $cmd) or die "$PROG: Can't run the \"find(1)\" command: $!\n"; # Get the directories while () { chomp; s,(.*)/.*,$1,; s,^./,,; s,/,.,g if ( $mode eq "p" ); $unique{$_}= undef if $_ =~ /\w+/; $toplevel= "." if $_ =~ /\./; } close(MYDIR); return ( keys(%unique) ) ; } sub get_parents($) { my @a = split /\./,$_[0]; my @ret = shift @a; push @ret, join '.', $ret[-1], $_ foreach @a; return @ret; }