[Accueil] - [Plan du site] - [Rechercher] - [ C O L R T S P ]  

 
Gérer sans effort la compilation d’un projet Kdevelop avec SCons
 

par nojhan le 22 mars 2007

Si vous utilisez Kdevelop (un environnement de programmation) et que vous aimez le principe de SCons (un outils de compilation, comme les autotools mais en bien), alors ces bouts de codes sont faits pour vous.

L’idée générale est de s’affranchir des immondes automake et autoconf, tout en ajoutant des fonctionnalités rigolotes, en étant partisant du moindre effort.

Pour ce faire, j’ai développé un petit script à coller dans votre SConstruct, qui facilite le travail.

Cela permet de
-  récupérer la liste des fichiers depuis le projet Kdevelop (qui deviens donc le seul point d’entré pour dire ce qui doit être compilé ou non).
-  gérer les options de compilations "à la SCons" (simplement, donc)
-  compiler dans des répertoires séparés, différents selon les options de compilations.
-  mettre automatiquement les sources (et elles-seules) dans un fichier ZIP.

Ce qui est fait

Ce code doit former le début de votre fichier SConstruct.


#!/usr/bin/env python


###################################################################################
#
#
# The aim of this script is to automatically compile your kdevelop
# project with the SCons construction tool.
#
# Features:
# * get the source file list from your kdevelop project
# * compile in separate build directories, one for each combination of build options
# * comes with a basic dist tool, that put all the sources in a zip archive
#
# Todo:
# * handle libraries
# * improve the dist part so that it can generate archives of binaries
# * handle config.h
# * get the project name from the .kdevelop file
# * ...
#
# Credits:
# * Author : Johann "nojhan" Dréo <nojhan@gmail.com>
# * Accumulate builder, from the SCons recipes : http://www.scons.org/wiki/AccumulateBuilder
#
###################################################################################


# Here start the generic code
# Normally, you should not change this part
# Look at the end of the SConstruct file for the interesting stuff

import os, shutil
import distutils.archive_util
import SCons

# setting the scons environnement
env = Environment()


def listdir(path,extensions=''):
        """Recursively list files matching a pattern (sort of "find . -name pattern")"""
        fichier=[]
        for root, dirs, files in os.walk(path):
                for g in extensions:
                        for f in glob.glob( root+'/*'+g ):
                                fichier.append( os.path.join(root, f) )
        return fichier


def zipperFunction(target, source, env):
        """Function to use as an action which creates a ZIP file from the arguments"""
        targetName = str(target[0])
        sourceDir = str(source[0])
        distutils.archive_util.make_archive(targetName, 'zip', sourceDir)


def copytree(src, dest, symlinks=False):
        """My own copyTree which does not fail if the directory exists.
       
        Recursively copy a directory tree using copy2().

        If the optional symlinks flag is true, symbolic links in the
        source tree result in symbolic links in the destination tree; if
        it is false, the contents of the files pointed to by symbolic
        links are copied.
       
        Behavior is meant to be identical to GNU 'cp -R'.       
        """
        def copyItems(src, dest, symlinks=False):
                """Function that does all the work.
               
                It is necessary to handle the two 'cp' cases:
                - destination does exist
                - destination does not exist
               
                See 'cp -R' documentation for more details
                """
                for item in os.listdir(src):
                   srcPath = os.path.join(src, item)
                   if os.path.isdir(srcPath):
                           srcBasename = os.path.basename(srcPath)
                           destDirPath = os.path.join(dest, srcBasename)
                           if not os.path.exists(destDirPath):
                                   os.makedirs(destDirPath)
                           copyItems(srcPath, destDirPath)
                   elif os.path.islink(item) and symlinks:
                           linkto = os.readlink(item)
                           os.symlink(linkto, dest)
                   else:
                           shutil.copy2(srcPath, dest)
                           
        # case 'cp -R src/ dest/' where dest/ already exists
        if os.path.exists(dest):
           destPath = os.path.join(dest, os.path.basename(src))
           if not os.path.exists(destPath):
                   os.makedirs(destPath)
        # case 'cp -R src/ dest/' where dest/ does not exist
        else:
           os.makedirs(dest)
           destPath = dest
        # actually copy the files
        copyItems(src, destPath)


def accumulatorFunction(target, source, env):
        """Function called when builder is called"""
        destDir = str(target[0])
        if not os.path.exists(destDir):
                os.makedirs(destDir)
        for s in source:
                s = str(s)
                if os.path.isdir(s):
                        myShutil.copytree(s, destDir, symlinks = False)
                else:
                        shutil.copy2(s, destDir)



class Maker:
        """The main class that handle all the complicated SConstruct stuff"""
       
        def __init__(self, name="a.out", version="0.0"):
                """In this constructors are defined the usefull build variables"""
               
                # name of the program
                # TODO: get the project name from the .kdevelop file
                self.name = name
               
                # version of the program
                self.version = version
               

                # Extensions of the source code files
                self.source_extensions = ['.cpp']


                # flags for optimization, use them in production
                #foptim = '-fcaller-saves -fcse-follow-jumps -fcse-skip-blocks  \
                #                  -felide-constructors -fexpensive-optimizations -ffast-math \
                #                  -ffloat-store -funroll-all-loops -funroll-loops'
                self.flags_optimization = '-O2 -mfpmath=sse' # better than -03 for me

                # flags for checking the code
                #-Wstrict-prototypes -Wmissing-prototypes
                self.flags_check = '-Wall -ansi -pedantic -Wimplicit -Wredundant-decls -Wreturn-type -Wunused'
                                 
                # flags for debugging
                self.flags_debug = '-ggdb -D _GLIBCXX_DEBUG '
               
                # more flags
                self.flags_sup = '' #-Wno-deprecated

                # Default directories
                self.dir_src = "src"
                self.dir_build = "build"
                self.dir_dist = "dist"

                # Path to libraries
                self.lib_path = ['/usr/lib','/usr/local/lib']
               
                # Libraries to use
                self.libs = []

                # default options
                self.options = Options()
                # Use the same scons way of adding options, on the 'options' member object
                self.options.AddOptions( \
                        BoolOption('debug', 'Compile with the debug informations',0) \
                )
                self.options.AddOptions( ('flags','Additional flags to pass to the compiler','') )
                # You must update the environment
                self.options.Update(env)

                # Grab the list of files
                self.sources = self.getKDevelopSourceList()
                self.files = self.getKDevelopFileList()


        def getKDevelopSourceList(self):
                """Extract the source file list from the kdevelop project files"""

                # Open project.kdevelop.filelist
                kdevlist = open(self.name+".kdevelop.filelist")
                l = []
                for f in kdevlist.readlines()[1:]:
                        f = f.strip()
                        # Only consider the specified extensions
                        for e in self.source_extensions:
                                if f.endswith( e ):
                                        l += [f]
                return l


        def getKDevelopFileList(self):
                """Extract the list of all the files from the kdevelop project files"""

                # Open project.kdevelop.filelist
                kdevlist = open(self.name+".kdevelop.filelist")
                l = []
                # Get all the files that are listed as part of the kdevelop project
                for f in kdevlist.readlines()[1:]:
                        l += [f.strip()]

                # Add the kdevelop project configuration files
                for f in os.listdir('.'):
                        if f.find(self.name+'.')==0 and not f.endswith('~'):
                                l += [f]

                return l


        def makePackagingSource(self):
                """Put the files of the projects in a new builder"""
                # TODO: improve the dist part so that it can generate archives of binaries
                       
                # add builder to accumulate files
                accuBuilder = env.Builder(action=accumulatorFunction,
                        source_factory=SCons.Node.FS.default_fs.Entry,
                        target_factory=SCons.Node.FS.default_fs.Entry,
                        multi=1)
                env['BUILDERS']['Accumulate'] = accuBuilder

                # add builder to zip files
                zipBuilder = env.Builder(action=zipperFunction,
                   source_factory=SCons.Node.FS.default_fs.Entry,
                   target_factory=SCons.Node.FS.default_fs.Entry,
                   multi=0)
                env['BUILDERS']['Zipper'] = zipBuilder

                # temporary directory where files are stored
                distDir = self.dir_dist
                #Execute(Delete(distDir))

                # base name of the package
                zipName = self.name+"-"+self.version

                # directory to use
                accDir = os.path.join(distDir,zipName)
       
                # Add the files
                env.Accumulate(accDir, self.files)

                # zip an archive
                env.Zipper(zipName, distDir)

                # use dist as target
                env.Alias(self.dir_dist,[zipName])



        def prepareBuild( self, name, envId, sources, sourceDir="src", buildDir = "build" ):
                """Automatic handling of several build directory with several options.
                ex.: name = 'prog', envId = 'debug', sources = ['src/lib1.cpp', 'src/lib2.cpp','src/main.cpp'], sourceDir = 'src'"""

                # target = build/debug/prog
                target = os.path.join(buildDir,envId,name)

                # sources = ['build/debug/lib1.cpp', 'build/debug/lib2.cpp','build/debug/main.cpp']
                sources = [f.replace(sourceDir+os.path.sep, buildDir+os.path.sep+envId+os.path.sep) for f in sources]

                return target, sources


        def make(self):
                """The SConstruct code"""
                # if debug, then add the debug options
                if env['debug']==1:
                        flags = " ".join( [self.flags_check,self.flags_sup,self.flags_debug,env['flags']] )
                else:
                        flags = " ".join( [self.flags_check,self.flags_sup,env['flags']] )

                # make the changes
                env.Replace( CCFLAGS = flags )
       
                # Generate the help
                Help( self.options.GenerateHelpText(env) )

                # list of the boolean options asked
                opts_list = [o.key for o in self.options.options if env[o.key]==1 ] # or env[o.key]==0]

                # ex.: a "prog-debug-network/" directory
                target_id = "-".join( [self.name] + opts_list )
               
                # Prepare the correct directories for building
                _target, _sources = self.prepareBuild( self.name, target_id, self.sources, self.dir_src, self.dir_build )
               
                # Apply in SCons
                env.BuildDir( os.path.join( self.dir_build, target_id ), self.dir_src )
               
                # The default target
                # TODO: handle libraries
                default = env.Program( target = _target, source = _sources, LIBPATH=self.lib_path, LIBS=self.libs  )

                # Make the dist target
                self.makePackagingSource()

                Default(default)

Ce qu’il faut faire soit même

Ça, c’est la partie qui change selon ce que vous voulez faire. J’ai mis un exemple que j’utilise.


###################################################################################
#
# Your own specific code
#
###################################################################################

# Instanciate the Maker class, that wrap all the boring work
m = Maker( name="eometah", version="0.1" )

# Pass some more flags to the compiler
# Here, the version as a precompiler constant
# TODO: handle config.h
m.flags_sup = " -D VERSION=\"\\\""+m.version+"\\\"\" "

# Add a fake option to the environment
# The options set to 0 are not specified when generating the build directories
# ex.: scons test=0 will be built in build/name
#      scons test=1 will be built in build/name-test
m.options.AddOptions(\
        BoolOption('test', 'Does nothing',0) \
)
m.options.Update(env)

# The configure part is too specific
# so you have to do it by yourself
conf = Configure(env)
if not ( conf.CheckLib('libeo') and conf.CheckLib('libeoutils') ):
        print 'EO (Evolving Objects) must be installed'
        Exit(1)
else:
        env.Replace( CPPPATH='/usr/local/include/eo/' )
        # Add some libs
        m.libs += ['eo','eoutils']
env = conf.Finish()

# Launch the whole thing
m.make()


Commentaires

Articles populaires

[Accueil] - [Plan du site] - [Rechercher] - [Admin.]       SPIP:Squelette