注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

liangxh2008的博客

 
 
 

日志

 
 

Java Makefile A simple replacement for Ant  

2010-05-17 20:40:53|  分类: java |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
http://geosoft.no/development/javamake.html

Abstract:
A cross-platform (MS-Windows, Linux, UNIX) makefile for a wide range of programming languages and tools (Java, C, C++, Fortran, Rmi, Javadoc, Makedepend etc.). Suitable for single file projects and scalable to multi million file projects. The Makefile is written once and for all; no further editing required. The file is well-organized and suitable as an example for learning GnuMake.

Table of Content


Introduction

The last few years integrated development environments (IDE) have become popular for software development. IDE's provides the developer with all the tools required for the development task such as file manager, version control system, modeling tools, editor, compiler, debugger, user interface builder, execution environment, profiler and performance analyzing tools. In particular the user interface builder part of IDE's has proved useful, and is the backbone of IDE's like VisualBasic, Delphi, VisualC++, VisualCafé to name a few.

In many cases however, it is far more efficient to do code development in a pure terminal oriented environment. This is done to avoid the vast overhead of the IDE itself, to achieve better control of the development process and to be able to choose development tools like editors and debuggers on an individual basis. For these reasons it is not uncommon to divide projects into UI modules and non-UI modules, and use IDE's only on the former.

Doing development in a pure environment requires a powerful make setup to be efficient. The makefile setup provided here is powerful yet simple. It was created to support large scale multi-platform development, but is equally well suited for the single source file project. Currently it supports Java, C, C++, Fortran but can easily be extended to any language or domain.

The makefiles in this document uses GnuMake syntax. GnuMake is the default make system on the Linux platform, it is available on all UNIX platforms, and a Microsoft Windows version can be downloaded from here. (Look for the file UnxUtils.zip. Download and unpack it, and make sure the wbin/ directory becomes part of your path.) The actual make command in GnuMake is sometimes called gnumake and sometime just make (as is the case on Linux). In this document the latter is used consistently.


Organizing the Development

Some directory is chosen to be the development root directory (denoted DEV_ROOT below), and underneath it should the following subdirectories be created:

  $DEV_ROOT/src
/obj
/lib
/docs
/make
/bin

The src/ directory holds source files (.java, .c++, .gif etc.) organized in units called packages. The directory structure should follow the Java convention of reversed domain name which ensures universally unique file identifiers. This is useful also for C/C++/Fortran based projects. For instance:

  $DEV_ROOT/src/com/adobe/...

The content of one directory constitutes one package. A typical package setup might be:

  $DEV_ROOT/src/com/adobe/illustrator/ui/window/
/illustrator/ui/dialogs/
/illustrator/ui/print/
/illustrator/ui/print/postscipt/
/illustrator/ui/print/pdf/
/acroread/editor/.../...
/acroread/editor/.../...
/...

For Java source, the package statement must always reflect the the name indicated by the directory of each source file.

The obj/ directory contains target files produced by the source files, and the directory structure will be identical to the one int the src tree. The sub structure will be automatically produced by make, but the obj directory itself must be created in advance.

The lib/ directory contains all libraries in use. This includes 3rd-party libraries (copied here manually), as well as libraries produced by make. For the latter, there will be one library per package (i.e. directory) within the source tree. Java source produce .jar libraries while C/C++/Fortran source produce .so libraries. The library is named according to the package location, so Java code in $DEV_ROOT/src/com/adobe/utils are put in $DEV_ROOT/lib/comadobeutil.jar and C code in $DEV_ROOT/src/no/geosoft/math/calculus are put in $DEV_ROOT/lib/nogeosoftmathcalculus.so and so on.

The docs/ directory will hold all output from automatic documentation tools like Javadoc or Doxygen. The entry point for the documentation depends on the tool, but for Javadoc it is the file $DEV_ROOT/docs/index.html.

The make/ directory will hold the main Makefile (shown below) and a script for producing package makefiles. It can optionally be used for holding make logs etc.

The bin/ directory is where make will put non-Java executables.


Prerequisites

The following three environment variables has to be set prior to the use of the make system given below:

  • DEV_ROOT - Pointing to the development root directory as described above. Use "/" as directory separator also on MS-Windows.

  • IS_UNIX - Should be set to true if the development platform is UNIX and left unset (default) if it is not. This variable is needed for the sole purpose of getting the file separator token used by the Java tools correct. (It is ; on MS-Windows and : on UNIX.)

  • JAVA_HOME - For Java development. Points to the Java distribution that is being used, for instance /usr/local/jdk1.3. All Java tools used by the make system is referred to through this pointer, thus changing to a different Java version is as simple as resetting this variable. Use "/" as directory separator also on MS-Windows.


The Makefiles

There are three different makefiles involved. The first two defines the project at hand and will differ for each project, while the third is generic and can be used as is:

  • Package Makefile - One for each package, located within the package directory, and containing the list of source files that constitute the package. These files are very simple, and can potentially be auto created.

  • Project Makefile - Located at $DEV_ROOT, listing all packages constitute the project, the executable class as well as documentation details.

  • Main Makefile - Located in the $DEV_ROOT/make directory and containing all the fine details about how to produce targets from source. This is the heart and the brain of the make system.


Package Makefile

The package makefiles should be called Makefile and should be located within the src tree, one per package. An example package makefile is shown below:

  Source = \
ByteSwapper.java \
Ebcdic.java \
Formatter.java \
IntMap.java \
javacup.gif \
properties.txt \

RmiSource =

Main = ByteSwapper

include $(DEV_ROOT)/Makefile

The Source entry lists all the source files. .java files will be passed to the Java compiler, .c files to the C-compiler and so on. Other files (like the .gif and .txt above) will be copied unprocessed to the obj tree.

The RmiSource entry lists all Java source files that are to be processed by the rmi compiler. Note that these files must also be listed under the Source entry as they are also processed by javac.

The Main entry is optional and indicates which class contains the main() method. Leave open if none of them do. For a Java program there is only one main entry point, but it is common to include main() in other classes in order to test that specific class. The setup above makes it possible to run these test applications by issuing "make run" from the package level. See below.

Project Makefile

The project makefile should be called Makefile and should be located at $DEV_ROOT. The project makefile lists all packages and jar archives that constitutes the project.

An example makefile for a typical Java project is shown below:

  JavaPackages = \
no/geosoft/directory \
no/geosoft/user \
no/geosoft/database \

JavaLibraries = \
mysql-connector.jar \

JavaMainClass = \
no.geosoft.database.Main

RunParameters =

# Javadoc

JavadocWindowTitle = 'Geotechnical Software Services - API Specification'
JavadocDocTitle = 'GeoSoft API'
JavadocHeader = 'GeoSoft API'
JavadocFooter = '<font size="-1">Copyright &copy; 2004 - Geotechnical Software Services <a href="http://geosoft.no">geosoft.no<a></font>'

include $(DEV_ROOT)/make/Makefile

A similar example for a typical C++ project:
  Packages = \
no/geosoft/directory \
no/geosoft/user \
no/geosoft/util \

IncludeDirs = \
/usr/include \
/usr/include/g++-2 \
/usr/X11R6/include \

LibraryDirs = \
/usr/lib \
/usr/X11R6/lib \

Libraries = \
mysql \


include $(DEV_ROOT)/make/Makefile

The JavaPackages entry lists all Java packages governed by this makefile. The Packages entry list all other packages.

The IncludeDirs list include directories referenced in C/C++/Fortran code. LibraryDirs and Libraries identifies .so libraries for C/C++/Fortran based linking, while JavaLibraries lists 3-rd party jars located in the $DEV_ROOT/lib directory.

The JavaMainClass and RunParameters are used for executing Java applications.

The Javadoc entries are optional and used for decoration of the produced Javadoc documentation.

Main Makefile

The Main makefile is the heart and brain of the make setup, and should be located in the $DEV_ROOT/make directory and be called Makefile. It is not executed directly, but rather included by the project makefile. This makefile contains everything needed to build the project described in the project makefile and it is rather complex. However, it it is written once and for all, and can to a large extent be left alone as is. Remember; This is the only Makefile you will ever need.

The Main Makefile is shown below. Copy-paste it from the browser and use it for your own projects:


#---------------------------------------------------------------------------
# (C) 1999 - 2004 Jacob Dreyer - Geotechnical Software Services
# jacob.dreyer@geosoft.no - http://geosoft.no
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston,
# MA 02111-1307, USA.
#---------------------------------------------------------------------------
#---------------------------------------------------------------------------
#
# GnuMake crash course:
#
# target : depends
# rule
#
# target - the parameter given to make. I.e. what to build
# depends - file or other targets target depends on
# rule - how to create target (note that rule is preceeded by a TAB char)
# $(VAR) - environment variable or variable defined above
# $@ - Current target
# $* - Current target without extension
# $< - Current dependency
#
#---------------------------------------------------------------------------
#---------------------------------------------------------------------------
#
# Directories
#
#---------------------------------------------------------------------------

SourceDir = $(DEV_ROOT)/src
TargetDir = $(DEV_ROOT)/obj
LibDir = $(DEV_ROOT)/lib
MakeDir = $(DEV_ROOT)/make
BinDir = $(DEV_ROOT)/bin
DocsDir = $(DEV_ROOT)/docs
CurrentDir = $(CURDIR)

ifdef Source
Package = $(subst $(SourceDir)/,,$(CurrentDir))
PackageList = $(Package)
PackageSourceDir = $(SourceDir)/$(Package)
PackageTargetDir = $(TargetDir)/$(Package)
JavaMainClass = $(subst /,.,$(Package)).$(Main)
else
PackageList = $(Packages) $(JavaPackages)
endif

PackageListLoop = $(patsubst %,$(SourceDir)/%/.loop,$(PackageList))

JRE = $(JAVA_HOME)/jre/lib/rt.jar

ifdef IS_UNIX
X = :
else
X = \;
endif


#---------------------------------------------------------------------------
#
# Classification of files
#
#---------------------------------------------------------------------------

# Source

JavaFiles = $(filter %.java, $(Source))
CppFiles = $(filter %.cc, $(Source))
CFiles = $(filter %.c, $(Source))
FortranFiles = $(filter %.f, $(Source))
CorbaFiles = $(filter %.idl, $(Source))
OtherSourceFiles = $(filter-out $(JavaFiles) $(CppFiles) $(CFiles) \
$(FortranFiles) $(CorbaFiles), \
$(Source))
ManifestFile = $(PackageSourceDir)/Manifest
SourceFiles = $(JavaFiles:%.java= $(PackageSourceDir)/%.java)\
$(CppFiles:%.cc= $(PackageSourceDir)/%.cc)\
$(CFiles:%.c= $(PackageSourceDir)/%.c)\
$(FortranFiles:%.f= $(PackageSourceDir)/%.f)


# Target

JavaClassFiles = $(JavaFiles:%.java= $(PackageTargetDir)/%.class)
JavaClassFilesRel = $(JavaFiles:%.java= $(Package)/%.class)
RmiStubFiles = $(RmiSource:%.java= $(PackageTargetDir)/%_Stub.class)
RmiSkeletonFiles = $(RmiSource:%.java= $(PackageTargetDir)/%_Skel.class)
JniClassFiles = $(JniSource:%.java= $(PackageTargetDir)/%.class)
JniHeaders = $(JniSource:%.java= $(PackageSourceDir)/%.h)
ObjectFiles = $(CFiles:%.c= $(PackageTargetDir)/%.o)\
$(CppFiles:%.cc= $(PackageTargetDir)/%.o)\
$(FortranFiles:%.f= $(PackageTargetDir)/%.o)
OtherTargetFiles = $(OtherSourceFiles:%=$(PackageTargetDir)/%)

ThirdPartyJarsTmp = $(patsubst %,$(LibDir)/%,$(JavaLibraries))
ThirdPartyJars = $(subst $(Space),$(X),$(ThirdPartyJarsTmp))

ifneq "$(words $(JavaFiles))" "0"
JavaPackageName = $(subst /,.,$(Package))
JarFile = $(LibDir)/$(subst /,,$(Package)).jar
endif
ifneq "$(words $(ObjectFiles))" "0"
DependencyFile = $(PackageSourceDir)/Makedepend
SharedLibrary = $(LibDir)/lib$(subst /,,$(Package)).so
StaticLibrary = $(LibDir)/lib$(subst /,,$(Package)).a
ifneq "$(Main)" ""
Executable = $(BinDir)/$(Main)
endif
endif

#
# Misc
#

ClassPath = $(JRE)$(X)$(TargetDir)$(X)$(ThirdPartyJars)
JavaPackageNames = $(subst /,.,$(JavaPackages))
IncludePath = -I$(SourceDir) $(IncludeDirs:%=-I%)
LibDirs = -L$(LibDir) $(LibraryDirs:%=-L%)
LocalLibs = $(subst /,,$(Packages))
LibList = $(LocalLibs:%=-l%) $(Libraries:%=-l%)



#---------------------------------------------------------------------------
#
# Tools & Options
#
#---------------------------------------------------------------------------

Print = @echo
Copy = cp
CCompiler = gcc
CppCompiler = gcc
Linker = gcc
MakeDepend = makedepend
MakeDir = mkdir -p
Delete = rm -fr
StaticArchiver = ar
DynamicArchiver = gcc
FortranCompiler = f77
JavaCompiler = $(JAVA_HOME)/bin/javac
JavaArchiver = $(JAVA_HOME)/bin/jar
JarSigner = $(JAVA_HOME)/bin/jarsigner
JavadocGenerator = $(JAVA_HOME)/bin/javadoc
JniCompiler = $(JAVA_HOME)/bin/javah
RmiCompiler = $(JAVA_HOME)/bin/rmic
JavaExecute = $(JAVA_HOME)/bin/java
Purify = purify
WordCount = wc
List = cat

MakeOptions = -k -s
MakeDependOptions =
StaticArchiverOptions = rc
DynamicArchiverOptions = -shared
JavaArchiverOptions =
JniOptions =
RmiOptions = -d $(TargetDir) -classpath $(ClassPath) \
-sourcepath $(SourceDir)
FortranOptions =
JavaCompilerOptions = -d $(TargetDir) -classpath $(ClassPath) \
-sourcepath $(SourceDir) -deprecation
JavaRunOptions = -classpath $(ClassPath)
PurifyOptions =
JavadocOptions = -d $(DocsDir) \
-sourcepath $(SourceDir) \
-classpath $(ClassPath) \
-author \
-package \
-use \
-splitIndex \
-version \
-link file:$(JAVA_HOME)/docs/api \
-windowtitle $(JavadocWindowTitle) \
-doctitle $(JavadocDocTitle) \
-header $(JavadocHeader) \
-bottom $(JavadocFooter)
WordCountOptions = --lines

Empty =
Space = $(Empty) $(Empty)



#---------------------------------------------------------------------------
#
# Rules
#
#---------------------------------------------------------------------------

default : build

%.loop :
@$(MAKE) $(MakeOptions) -C $(subst .loop,,$@) _$(MAKECMDGOALS)all

# Create target directory
$(PackageTargetDir) :
$(MakeDir) $@

# .c -> .o
$(PackageTargetDir)/%.o : $(PackageTargetDir) $(PackageSourceDir)/%.c
$(Print) $@
@$(CCompiler) $(COptions) -c $(IncludePath) $< -o $@

%.o : $(PackageSourceDir)/%.c
$(MAKE) $(MakeOptions) $(PackageTargetDir)/$@

# .cc -> .o
$(PackageTargetDir)/%.o : $(PackageSourceDir)/%.cc
$(Print) $@
$(CppCompiler) $(CppOptions) -c $(IncludePath) $< -o $@

%.o : $(PackageSourceDir)/%.cc
$(MAKE) $(MakeOptions) $(PackageTargetDir)/$@

# .f -> .o
$(PackageTargetDir)/%.o : $(PackageSourceDir)/%.f
$(Print) $@
@$(FortranCompiler) $(FortranOptions) -c $< -o $@

%.o : $(PackageSourceDir)/%.f
$(MAKE) $(MakeOptions) $(PackageTargetDir)/$@

# .java -> .class
$(PackageTargetDir)/%.class : $(PackageSourceDir)/%.java
$(Print) $@
@$(JavaCompiler) $(JavaCompilerOptions) $<

%.class : $(PackageSourceDir)/%.java
@$(MAKE) $(MakeOptions) $(PackageTargetDir)/$@

# .class -> .h
$(PackageSourceDir)/%.h : $(PackageTargetDir)/%.class
$(Print) $@
$(JniCompiler) $(JniOptions) $(JavaPackageName).$*

%.h : %.class
$(MAKE) $(MakeOptions) $(PackageSourceDir)/$@

# .o -> .a
$(LibDir)/%.a : $(ObjectFiles)
$(Print) $@
@$(StaticArchiver) $(StaticArchiverOptions) $@ $(ObjectFiles)

%.a : $(ObjectFiles)
$(MAKE) $(MakeOptions) $(LibDir)/$@

# .o -> .so
$(LibDir)/%.so : $(ObjectFiles)
$(Print) $@
$(DynamicArchiver) $(ObjectFiles) $(DynamicArchiverOptions) -o $@

%.so : $(ObjectFiles)
$(MAKE) $(MakeOptions) $(LibDir)/$@

# .class -> .jar
$(LibDir)/%.jar : $(JavaClassFiles) $(OtherTargetFiles)
$(Print) $@
@cd $(TargetDir); $(JavaArchiver) -cf $@ \
$(JavaClassFilesRel) $(OtherTargetFiles)

%.jar : $(JavaClassFiles) $(OtherTargetFiles)
$(MAKE) $(MakeOptions) $(LibDir)/$@

# .class -> JavaDoc
javadoc :
$(Print) $(JavaPackageNames) > $(DEV_ROOT)/packages.tmp
$(JavadocGenerator) $(JavadocOptions) @$(DEV_ROOT)/packages.tmp
$(Delete) $(DEV_ROOT)/packages.tmp
$(Print) Done JavaDoc.

# .class -> _Stub.class
$(PackageTargetDir)/%_Stub.class : $(PackageTargetDir)/%.class
$(Print) $@
$(RmiCompiler) $(RmiOptions) $(JavaPackageName).$*

%_Stub.class : %.class
$(MAKE) $(MakeOptions) $(PackageTargetDir)/$@

# .class -> _Skel.class
$(PackageTargetDir)/%_Skel.class : $(PackageTargetDir)/%.class
$(Print) $@
$(RmiCompiler) $(RmiOptions) $(JavaPackageName).$*

%_Skel.class : %.class
$(MAKE) $(MakeOptions) $(PackageTargetDir)/$@

# Executable
$(Executable) : $(ObjectFiles)
$(Print) $@
$(Linker) $(LinkOptions) $(LibDirs) $(LibList) $(ObjectFiles) -o $@

# Anything else is just copied from source to target
$(PackageTargetDir)/% : $(PackageSourceDir)/%
$(Print) $@
$(Copy) $< $@

# make (or make build)
build : $(PackageListLoop)
$(Print) Done build.

_all : _buildall

_buildall : \
$(DependencyFile) \
$(PackageTargetDir) \
$(ObjectFiles) \
$(JavaClassFiles) \
$(RmiStubFiles) \
$(RmiSkeletonFiles) \
$(OtherTargetFiles) \
$(SharedLibrary) \
$(StaticLibrary) \
$(JarFile) \
$(Executable)


# make clean
clean : $(PackageListLoop)
$(Print) Done clean.

_cleanall :
$(Delete) $(PackageTargetDir)/* \
$(JarFile) \
$(SharedLibrary) \
$(StaticLibrary) \
$(Executable) \
$(DependencyFile)


# make depend
depend : $(PackageListLoop)
$(Print) Done dependencies.

_dependall : $(DependencyFile)

$(DependencyFile) :
$(Print) $@
@cd $(PackageSourceDir); \
$(MakeDepend) $(MakeDependOptions) -f- -p$(PackageTargetDir)/ \
$(IncludePath) $(Source) > $(DependencyFile)

# make lib
lib : $(PackageListLoop)
$(Print) Libraries built.

_liball : $(JarFile) $(SharedLibrary) $(StaticLibrary)

jar : $(JarFile)

jarsign : $(JarFile)
$(JarSigner) -keystore GeoSoftKeystore $(JarFile) myself

# make statistics
_statisticsall :
@$(Print) $(SourceFiles) >> $(DEV_ROOT)/files.tmp

statistics : $(PackageListLoop)
@$(List) $(DEV_ROOT)/files.tmp | xargs $(WordCount) $(WordCountOptions)
@$(Delete) $(DEV_ROOT)/files.tmp
$(Print) Done statistics.

# make pure
$(Executable).pure :
$(Purify) $(PurifyOptions) $(CppCompiler) $(LinkOptions) $(LibDirs) \
$(LibList) $(ObjectFiles) -o $@

pure : $(Executable).pure

# Execute
_runexe :
$(Executable) $(RunParameters)

_runjava :
$(JavaExecute) $(JavaRunOptions) $(JavaMainClass) $(RunParameters)

run : _runjava


ifdef $(DependencyFile)
-include $(DependencyFile)
endif


Running Make

Using the make system is quite simple. You will run it from either the package level or from the project level ($DEV_ROOT).

The following commands can be applied from the package level (i.e when standing in a given package directory within the src tree):

   make - Process all source files in the package

   make clean - Remove all target files of the package

   make SomeTarget - Produce a specific file like Math.class or math.o. etc.

   make run - Execute the Java class identified by Main in the package Makefile.

The following commands can be applied from the project level (i.e while standing in the $DEV_ROOT directory):

   make - Process all source files in all packages

   make clean - Remove all produced files in all packages

   make lib - Create all libraries

   make javadoc - Create documentation for entire project

   make run - Run application

   make depend - Create dependency information (non-Java)

   make statistics - Produce LOC statistics for the entire project


A Package Makefile Generator

A package makefile is nothing but a list of the files in the package directory. It can easily be generated automatically and the following script do just that. The script should be run while standing in the package directory.

  #!/bin/csh -f

#***************************************************************************
#
# Makefile generator script
#
# (C) 2004 Jacob Dreyer - Geotechnical Software Services
# jacob.dreyer@geosoft.no - http://geosoft.no
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
#***************************************************************************

set file = Makefile

rm -f $file
touch $file

printf "Source = \\\n" >> $file

foreach SourceFile (ls *.c *.cc *.java *.f *.gif *.jpg`)
printf "\t%s \\\n" ${SourceFile} >> $file
end
printf "\n" >> $file

printf "RmiSource =\n\n" >> $file
printf "Main =\n\n" >> $file
printf "include %s" '$(DEV_ROOT)/Makefile >> $file
Note that the RmiSource and the Main tags cannot be set automatically and, if required, these should be added after the script has been run.
  评论这张
 
阅读(965)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017