Jakie są dobre sposoby organizowania plików wejściowych (Makefiles, SConstruct, CMakeLists.txt itp.) W celu zbudowania oprogramowania do automatyzacji?

13

Jedną z rzeczy, które lubię robić z moim kodem, jest upewnienie się, że został on przekształcony w części łatwe do zarządzania. Jednak jeśli chodzi o tworzenie oprogramowania, okazuje się, że cokolwiek oprogramowania do automatyzacji budowania, którego używam (ostatnio GNU Make lub SCons), staje się kompletnym bałaganem. Pliki wejściowe wyglądają jak długie skrypty, które wydają się przeciwstawiać łatwemu refaktoryzacji. Chciałbym móc je w jakiś sposób refaktoryzować, ale pojęcie „funkcji” nie zachowuje się tak samo w niektórych programach do automatyzacji kompilacji, jak w języku programowania, więc trudno mi napisać Pliki makefile lub pliki SConscript dla wszystkich projektów, które są umiarkowanie skomplikowane.

Czy ktoś ma jakieś porady na temat pisania zarządzalnych plików wejściowych dla oprogramowania do automatyzacji kompilacji? Najlepsza byłaby porada niezależna od oprogramowania, ale nawet porady dotyczące konkretnego narzędzia do automatyzacji kompilacji byłyby pomocne, szczególnie Marka lub SCony, ponieważ tego właśnie używałem w projektach.

Edycja: Jak wskazał Thorbjørn, powinienem dodać kilka kontekstów i przykładów użycia. Kończę doktorat z inżynierii chemicznej i prowadzę badania w dziedzinie informatyki. (Jestem profesjonalistą w SciComp.SE, dla tych z was, którzy odwiedzają.) Moje projekty zwykle obejmują mieszankę skompilowanych języków (C, C ++, Fortran), które wykonują niektóre ciężkie języki skryptowe (Python , Perl) do prototypowania, a czasami także języki specyficzne dla domeny do prototypowania lub do celów technicznych.

Dodałem dwa przykłady poniżej, z grubsza w zakresie 250 linii. Problemem jest dla mnie ogólnie brak modułowości. Niektóre z tych projektów można zorganizować w jednostki modułowe i byłoby miło wyodrębnić części kompilacji według tych zasad, aby ułatwić sobie i przyszłym opiekunom śledzenie. Podział każdego skryptu na wiele plików to jedno rozwiązanie, nad którym bawiłem się w mojej głowie.

Drugi przykład jest szczególnie ważny, ponieważ wkrótce będę musiał zgromadzić dużą liczbę plików.

Oto, jak Makefilemogłaby wyglądać dla mnie linia 265 , wzięta z rzeczywistego projektu i zorganizowana najlepiej, jak potrafię:

#!/usr/bin/make
#Directory containing DAEPACK library folder
daepack_root = .
library = $(daepack_root)/lib
wrappers = $(daepack_root)/Wrappers/DSL48S
c_headers = parser.h problemSizes.h
f77_headers=problemSizes.f commonParam.f
f90_headers=problemSizes.f commonParam.f90
includes = -I. -Iinclude -I/usr/include/glib-2.0 \
    -I/usr/lib/glib-2.0/include -I/usr/include/libxml2 \
    -I/usr/include/libgdome -I/usr/include/gtest/

#Fortran 77 environment variables
f77=gfortran
fflags=-ggdb -cpp -fno-second-underscore --coverage -falign-commons \
    -mcmodel=large -fbacktrace -pg 
flibs=

#Fortran 90 environment variables
f90=gfortran
f90flags=-ggdb -cpp -fno-second-underscore --coverage -falign-commons \
    -mcmodel=large -fbacktrace -pg 
f90libs=

#C environment flags
cc=gcc
cflags=-ggdb --coverage $(includes) -mcmodel=large 
clibs=

#Libraries for linking
libs=-L$(library) -ldaepack_sparse -lblas -llapack -ldl -lg2c \
    -lgdome -lxml2 -lgtest -lcunit -lcholmod -lamd -lcolamd -lccolamd \
    -lmetis -lspqr -lm -lblas -llapack -lstdc++ -lpcre

#Object files
objs=main.o $(dsl48sObjs) $(gdxObjs)
gdxObjs = gdxf9def.o gdxf9glu.o gamsglobals_mod.o 
commonObjs=libdsl48s_model.sl cklib.o parser.o $(gdxObjs)
originalModelObjs=originalModel.o dsl48sChemkinModule.o $(commonObjs)
cspSlowModelObjs=cspSlowModel.o dsl48sChemkinModuleSlow.o cspModule.o \
    $(commonObjs)
orthoProjModelObjs=orthoProjModel.o dsl48sChemkinModuleOrthoProj.o \
    orthoProjModule.o basisModule.o spqrUtility.o $(commonObjs)

#Shell environment variable definitions for FUnit
FCFLAGS := $(f90flags)
LDFLAGS := libdsl48s_model.sl cklib.o gdxf9glu.o parser.o spqrUtility.o \
    $(libs)

misc=*table *size.f 
output=*.out

#Ftncheck flags for static analysis of Fortran 77 code
ftnchekflags= -declare -include=. -library -style=block-if,distinct-do,do-enddo,end-name,goto,labeled-stmt,structured-end

all: ckinterp.exe parserTest.exe originalModel.exe cspSlowModel.exe \
    orthoProjModel.exe spqrUtilityTest.exe
#Check code style with lexical analyzer
    @echo Checking program style...
    ftnchek $(ftnchekflags) rhs.f
    ftnchek $(ftnchekflags) resorig.f
    ftnchek $(ftnchekflags) res.f
#   ftnchek $(ftnchekflags) cklib.f
#   ftnchek $(ftnchekflags) ckinterp.f
#Set up baseline coverage data file
    @echo Set up baseline coverage data file
    lcov -c -i -d . -o conpDSL48Sbase.info
#Run unit test on cspModule.f90
    @echo Running unit tests on cspModule.f90...
    funit cspModule
#Generate test coverage data for cspModule.f90
    @echo Generating test coverage data from cspModule.f90 tests...
    lcov -c -d . -o conpDSL48ScspTest.info
#Run unit test on orthoProjModule.f90
    @echo Running unit tests on orthoProjModule.f90...
    funit orthoProjModule
#Generate test coverage data for orthoProjModule.f90
    @echo Generating test coverage data from orthoProjModule.f90 tests...
    lcov -c -d . -o conpDSL48SgenProjTest.info
#Run unit tests on the parser C library
    @echo Running unit tests on parser in C...
    -G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind -v --tool=memcheck \
    --leak-check=full --show-reachable=yes --leak-resolution=high \
    --num-callers=20 --log-file=parserTest.vgdump \
    ./parserTest.exe > parserTest.log
#Generate test coverage data for the parser wrapper C library
    @echo Generating test coverage data for the parser in C...
    lcov -c -d . -o conpDSL48SparserTest.info
#Run unit tests on the SparseQR C library
    @echo Running unit tests on SparseQR library in C...
    ./spqrUtilityTest.exe
#Generate test coverage data for the SparseQR C library
    @echo Generating test coverage data for the SparseQR C library...
    lcov -c -d . -o conpDSL48SsparseTest.info
#Run unit test on basisModule.f90
    @echo Running unit tests on basisModule.f90...
    funit basisModule
#Generate test coverage data for basisModule.f90
    @echo Generating test coverage data from basisModule.f90 tests...
    lcov -c -d . -o conpDSL48SbasisMod.info
#Combine test coverage data
    @echo Combine baseline and test coverage data...
    lcov -a conpDSL48Sbase.info \
    -a conpDSL48ScspTest.info \
    -a conpDSL48SgenProjTest.info \
    -a conpDSL48SbasisMod.info \
    -a conpDSL48SparserTest.info \
    -a conpDSL48SsparseTest.info \
    -o conpDSL48Stotal.info
#Post-process to remove coverage statistics from automatically 
#generated source code.
    @echo Removing coverage statistics for automatically generated source...
    lcov -r conpDSL48Stotal.info basisModule_fun.f90 \
    ckinterp.f cklib.f cspModule_fun.f90 davisSkodjeAd.f90 \
    davisSkodjeJac.f90 davisSkodjeRes.f90 davisSkodjeRhs.f90 \
    davisSkodjeSp.f90 gdxf9def.f90 gdxf9glu.c orthoProjModule_fun.f90 \
    jac.f jacorig.f resad.f resadp.f resorigad.f resorigadp.f ressp.f \
    resorigsp.f senrhs.f senrhsorig.f TestRunner.f90 \
    -o conpDSL48Stotal.info
#Generate HTML report of coverage data
    @echo Generate HTML report of coverage data...
    genhtml conpDSL48Stotal.info
    @echo Open "index.html" in browser for coverage results!

originalModel.exe: $(originalModelObjs) $(f90_headers) $(f77_headers) \
    $(c_headers)
    $(f90) $(f90flags) -o originalModel.exe $(originalModelObjs) $(libs)

originalModel.o: dsl48sChemkinModule.o $(commonObjs) $(f77_headers) \
    $(f90_headers) $(c_headers)
    $(f90) $(f90flags) -c -o originalModel.o originalModel.f90

cspSlowModel.exe: $(cspSlowModelObjs) $(f90_headers) $(f77_headers) \
    $(c_headers)
    $(f90) $(f90flags) -o cspSlowModel.exe $(cspSlowModelObjs) $(libs)

cspSlowModel.o: dsl48sChemkinModuleSlow.o cspModule.o $(commonObjs) \
    $(c_headers) $(f77_headers)
    $(f90) $(f90flags) -c -o cspSlowModel.o cspSlowModel.f90

orthoProjModel.exe: $(orthoProjModelObjs) $(f90_headers) $(f77_headers) \
    $(c_headers) resOrthoFast.o
    $(f90) $(f90flags) -o orthoProjModel.exe $(orthoProjModelObjs) \
    resOrthoFast.o $(libs)

orthoProjModel.o: dsl48sChemkinModuleOrthoProj.o orthoProjModule.o $(commonObjs) \
    $(c_headers) $(f90_headers) $(f77_headers) resOrthoFast.o basisModule.o
    $(f90) $(f90flags) -c -o orthoProjModel.o orthoProjModel.f90

dsl48sChemkinModule.o: dsl48sChemkinModule.f90 cklib.o problemSizes.h \
    parser.o $(c_headers) $(f90_headers)
    $(f90) $(f90flags) -c -o dsl48sChemkinModule.o dsl48sChemkinModule.f90 

dsl48sChemkinModuleSlow.o: dsl48sChemkinModuleSlow.f90 cspModule.o cklib.o \
    problemSizes.h parser.o $(c_headers) $(f90_headers)
    $(f90) $(f90flags) -c -o dsl48sChemkinModuleSlow.o \
    dsl48sChemkinModuleSlow.f90

dsl48sChemkinModuleOrthoProj.o: dsl48sChemkinModuleOrthoProj.f90 \
    orthoProjModule.o basisModule.o cklib.o problemSizes.h \
    parser.o $(c_headers) $(f90_headers)
    $(f90) $(f90flags) -c -o dsl48sChemkinModuleOrthoProj.o \
    dsl48sChemkinModuleOrthoProj.f90

basisModule.o: basisModule.f90 cklib.o spqrUtility.o commonParam.f90
    $(f90) $(f90flags) -c -o basisModule.o basisModule.f90

spqrUtility.o: spqrUtility.h spqrUtility.c
    $(cc) $(cflags) -c -o spqrUtility.o spqrUtility.c

spqrUtilityTest.exe: spqrUtilityTest.o spqrUtility.o
    $(cc) $(cflags) -o spqrUtilityTest.exe spqrUtilityTest.o \
    spqrUtility.o $(libs)

spqrUtilityTest.o: spqrUtilityTest.c spqrUtility.o
    $(cc) $(cflags) -c -o spqrUtilityTest.o spqrUtilityTest.c

cklib.o: cklib.f ckstrt.f
    $(f77) $(fflags) -c -o cklib.o cklib.f

ckinterp.exe: ckinterp.o
    $(f77) $(fflags) -o ckinterp.exe ckinterp.o

ckinterp.o: ckinterp.f
    $(f77) $(fflags) -c -o ckinterp.o ckinterp.f

#Recursive makefile inherited from previous graduate students
libdsl48s_model.sl: $(f77_headers) cklibDAEPACK.f
    cp $(wrappers)/makefile model.mk
    make -f model.mk

resOrthoFast.o: libdsl48s_model.sl
    $(f90) $(f90flags) -c -o resOrthoFast.o resOrthoFast.f90

problemSizes.f: problemSizes.fpp problemSizes.h
    cpp problemSizes.fpp problemSizes.f
    perl -p -i.bak -we 's/# /! /;' problemSizes.f

commonParam.f90: commonParam.f
    perl -p -i.bak -we 's/^#/!/;' commonParam.f
    echo "commonParam t f t fpp" | pref77tof90
    echo "commonParam /" | f77tof90
    perl -p -i.bak -we 's/integer a/!integer a/;' commonParam.f
    perl -p -i.bak -we 's/END   //;' commonParam.f90

commonParam.f: commonParam.fpp problemSizes.h
    cpp commonParam.fpp commonParam.f
    perl -p -i.bak -we 's/^#/!/;' commonParam.f

cspModule.o: cspModule.f90
    $(f90) $(f90flags) -c -o cspModule.o cspModule.f90

orthoProjModule.o: gamsglobals_mod.o gdxf9def.o gdxf9glu.o orthoProjModule.f90 \
    formatLabels.f90
    $(f90) $(f90flags) -c -o orthoProjModule.o orthoProjModule.f90

gdxf9def.o: gdxf9def.f90
    $(f90) $(f90flags) -c -o gdxf9def.o gdxf9def.f90

gdxf9glu.o: gdxf9glu.c gdxf9def.o
#64-bit version of wrappers (with underscores)
    $(cc) $(cflags) -DCIA_LEX -DAPIWRAP_LCASE_DECOR -c -o \
    gdxf9glu.o gdxf9glu.c
#64-bit version of wrappers (without underscores, for C interoperability)
#   $(cc) $(cflags) -DCIA_LEX -DAPIWRAP_LCASE_NODECOR -c gdxf9glu.c
#32-bit version of wrappers
#   $(cc) $(cflags) -DAPIWRAP_LCASE_DECOR -c gdxf9glu.c -Iinclude

gamsglobals_mod.o: gamsglobals_mod.f90 gdxf9def.o gdxf9glu.o
    $(f90) $(f90flags) -c gamsglobals_mod.f90

parser.o: parser.c $(c_headers)
    $(cc) $(cflags) -c -o parser.o parser.c 

parserTest.exe: parserTest.o parser.o
    $(cc) $(cflags) -o parserTest.exe parser.o \
    parserTest.o $(libs)

parserTest.o: parserTest.cpp parser.o
    $(cc) $(cflags) -c -o parserTest.o parserTest.cpp

clean:
    -rm *.bak
    -rm *.f77
    -rm *.log
    -rm commonParam.f90
    -rm problemSizes.f
    -rm commonParam.f
    -make clean -f model.mk
    -rm model.mk
    -rm *.o
    -rm *.mod
    -rm $(misc)
    -rm *.exe
    -funit --clean
    -rm *.gcno
    -rm *.gcda
    -rm *.info
    -rm *.png
    -rm *.html
    -rm *.css
    -rm -rf html
    -rm *.pyc
    -rm *.lst

Oto 245-liniowy SConstructplik, który próbuję obecnie zorganizować dla projektu, który jest mniej więcej tak złożony:

## \file SConstruct
#  \brief Compiles the library and compiles tests.
#

import SCons

## \brief Build up directory names of each COIN library from package names
#         and versions.
#

## Overall SCons environment
#
env = Environment();

flags = []

## Compile using debug versions?
#
debug = True
debugString = '-debug'
debugFlags = ['-ggdb']

dynamicLinkFlag = '-Wl,-rpath,'

if debug:
    flags += debugFlags

## Compile Google Test from scratch.
#
GTestVersion = '1.6.0'
GTestStem = 'gtest-' + GTestVersion
GTestBuildIncDir = [GTestStem,
                    GTestStem + '/include',
                    ]
GTestAllLib = env.Library('lib/libgtest.a', 'gtest-1.6.0/src/gtest-all.cc',
                      CPPPATH = GTestBuildIncDir,
                      CXXFLAGS = flags)
GTestMainLib = env.Library('lib/libgtest_main.a',
                           'gtest-1.6.0/src/gtest_main.cc',
                           CPPPATH = GTestBuildIncDir,
                           CXXFLAGS = flags)

GTestIncDir = GTestStem + '/include/gtest'
GTestLibDir = 'lib'
GTestLibFlags = ['gtest', 'gtest_main', 'pthread']

## Armadillo matrix library
#
ArmadilloLibFlags = ['armadillo'];

## Quick reminder of SCons flags:
#  CPPPATH = path of headers (include directories)
#  LIBPATH = path of libraries
#  LIBS = flags of libraries
#  CXXFLAGS = C++ compilation flags
#




## Locations of libraries installed on system in standard locations
#
StdIncDir = '/usr/include'
StdLibDir = '/usr/lib'

## Configuration information for COIN libraries
#
CoinUtilsVersion = '2.6.4'
ClpVersion = '1.12.0'
OsiVersion = '0.103.0'
CbcVersion = '2.5.0'

## Some standard directory locations of COIN libraries, with slashes added for
#  for convenience.
#
CoinLibLocation = '/usr/local/COIN/'
StdCoinIncDir = '/include/coin'
StdCoinLibDir = '/lib'

CoinUtilsStem = 'CoinUtils-' + CoinUtilsVersion
ClpStem = 'Clp-' + ClpVersion
OsiStem = 'Osi-' + OsiVersion
CbcStem = 'Cbc-' + CbcVersion

if debug:
    CoinUtilsStem += debugString
    CbcStem += debugString
    ClpStem += debugString
    OsiStem += debugString


## Build up include directory names for COIN projects from constituent parts.
#
CoinUtilsIncDir = CoinLibLocation + CoinUtilsStem + StdCoinIncDir
ClpIncDir = CoinLibLocation + ClpStem + StdCoinIncDir
OsiIncDir = CoinLibLocation + OsiStem + StdCoinIncDir
CbcIncDir = CoinLibLocation + CbcStem + StdCoinIncDir

## Build up library names from COIN projects from constituent parts
#
CoinUtilsLibDir = CoinLibLocation + CoinUtilsStem + StdCoinLibDir
ClpLibDir = CoinLibLocation + ClpStem + StdCoinLibDir
OsiLibDir = CoinLibLocation + OsiStem + StdCoinLibDir
CbcLibDir = CoinLibLocation + CbcStem + StdCoinLibDir

## CPLEX
#
CpxStem = '/opt/ibm/ILOG/CPLEX_Studio_Academic123/cplex/'
CpxIncDir = CpxStem + 'include/ilcplex'
CpxLibDir = CpxStem + 'lib/x86-64_sles10_4.1/static_pic'

## Gurobi
# 
GrbStem = '/opt/gurobi460/linux64/'
GrbIncDir = GrbStem + 'include'
GrbLibDir = GrbStem + 'lib'

OsiLibFlags = ['Osi', 'CoinUtils']
ClpLibFlags = ['Clp', 'OsiClp']
CbcLibFlags = ['Cbc', 'Cgl']
OsiCpxLibFlags = ['OsiCpx']
OsiGrbLibFlags = ['OsiGrb']
CpxLibFlags = ['cplex', 'ilocplex', 'pthread', 'm']
GrbLibFlags = ['gurobi_c++', 'gurobi46', 'pthread', 'm']

milpIncDirs = [CoinUtilsIncDir,
               ClpIncDir,
               OsiIncDir,
               CbcIncDir,
               CpxIncDir,
               GrbIncDir,
            GTestIncDir,
               ]
milpLibDirs = [CoinUtilsLibDir,
               ClpLibDir,
               OsiLibDir,
               CbcLibDir,
               CpxLibDir,
               GrbLibDir,
               GTestLibDir,
            ]
milpLibFlags = [OsiCpxLibFlags,
                OsiGrbLibFlags,
                CbcLibFlags,
                ClpLibFlags,
                OsiLibFlags,
                CpxLibFlags,
                GrbLibFlags,
                GTestLibFlags,
                ]
##milpSolver = env.Object('milpSolver.cpp',
            ##                         CPPPATH = milpIncDirs,
##                         LIBPATH = milpLibDirs,
##                         CXXFLAGS = flags)
milpSolverTest = env.Program('milpSolverUnitTest',
                                  ['milpSolverTest.cpp',
                                   'milpSolver.cpp'],
                                  CPPPATH = milpIncDirs,
                                  LIBPATH = milpLibDirs,
                                  LIBS = milpLibFlags,
                                  CXXFLAGS = flags,
                                  LINKFLAGS = ['-Wl,-rpath,' + OsiLibDir])
env.Depends(milpSolverTest, [GTestAllLib, GTestMainLib])

## Chemkin source directories and files
#
ChemkinSourceDir = '/mnt/hgfs/DataFromOldLaptop/Data/ModelReductionResearch/Papers/AdaptiveChemistryPaper/AdaptiveChemistry/NonOpenSource/ChemkinII/';
ChemkinSourceList = ['cklib.f', 'pcmach.f','tranlib.f']
ChemkinSourceList = [ChemkinSourceDir + FileName
                     for FileName in ChemkinSourceList]
env.Depends('cklib.f','ckstrt.f')

## Cantera include directorie
#
CanteraStem = '/usr/local/cantera'

if debug:
    CanteraStem += debugString

CanteraIncDir = CanteraStem + '/include/cantera'
CanteraLibDir = CanteraStem + '/lib'
CanteraTestingFlags = ['kinetics', 'thermo', 'tpx', 'ctbase', 'm',]
CanteraLibFlags = ['user', 'oneD', 'zeroD', 'equil', 'kinetics', 'transport',
                    'thermo', 'ctnumerics', 'ctmath', 'tpx', 'ctspectra',
                    'converters', 'ctbase', 'cvode', 'ctlapack', 'ctblas',
                    'ctf2c', 'ctcxx', 'ctf2c', 'm', 'm', 'stdc++']

CxxFortranFlags = ['g2c', 'gfortran']; 

chemSolverIncDir = [CanteraIncDir,
                    StdIncDir,
                    '/usr/local/include',
                    GTestIncDir,
                    ]
chemSolverLibDir = [StdLibDir,
                    CanteraLibDir,
                    GTestLibDir,
                    ]
chemSolverLibFlags = [GTestLibFlags,
                      CxxFortranFlags,
                      CanteraLibFlags,
                      ArmadilloLibFlags,
                      ]

chemSolverTest = env.Program('chemSolverUnitTest',
                        ['chemSolverTest.cpp',
                         'chemSolver.cpp',
                         'ckwrapper.f90'] + ChemkinSourceList,
                        CPPPATH = chemSolverIncDir,
                        LIBPATH = chemSolverLibDir,
                        LIBS = chemSolverLibFlags,
                        CXXFLAGS = flags,
                        FORTRANFLAGS = flags,
                        F90FLAGS = flags)
env.Depends(chemSolverTest, [GTestAllLib, GTestMainLib])

#env.AddPostAction(milpSolverTest, milpSolverTest[0].abspath)
testAlias = env.Alias('test', [milpSolverTest, chemSolverTest])
AlwaysBuild(testAlias)

ckInterp = env.Program('ckinterp', ChemkinSourceDir + 'ckinterp.f')

canteraGTestLibFlags = CanteraTestingFlags + GTestLibFlags

#canteraGTestLibFlags = ['kinetics', 'thermo', 'tpx',
#                        'ctbase',  'm', 'gtest', 'gtest_main', 'pthread']

canteraGTest = env.Program('canteraGTest',
                           'canteraGTest.cpp',
                           CPPPATH = chemSolverIncDir,
                           LIBPATH = chemSolverLibDir,
                           LIBS = canteraGTestLibFlags,
                           CXXFLAGS = flags)
env.Depends(canteraGTest, [GTestAllLib, GTestMainLib])

canteraMemTestLibFlags = CanteraTestingFlags

canteraMemTest = env.Program('canteraMemTest',
                             'canteraMemTest.cpp',
                             CPPPATH = chemSolverIncDir,
                             LIBPATH = chemSolverLibDir,
                             LIBS = canteraMemTestLibFlags,
                             CXXFLAGS = flags)
Geoff Oxberry
źródło

Odpowiedzi:

8

Strategia refaktoryzacji

Oprócz komentarzy dietbuddha i ThorbjørnRavnAnderson, innym sposobem na refaktoryzację skryptów kompilacji jest podzielenie ich na wiele plików. To, jak to zrobisz, zależy od systemu kompilacji.

W przypadku Make jest to tak proste, jak użycie includepolecenia, zgodnie z zaleceniami w „Recursive Make Uważane za szkodliwe” . Ta dyrektywa działa tak jak #includew preprocesorze C i przetwarza dołączony plik tak, jakby został wycięty i wklejony w miejsce includepolecenia. Za pomocą includepolecenia można refaktoryzować główny element , Makefileprzenosząc elementy modułowe na podrzędne Makefile.

CMake ma podobne polecenie.

SCons wymaga podobnego rodzaju podejścia z różnymi poleceniami. Podstawowa idea podzielenia skryptu kompilacji na skrypt główny i kilka mniejszych podskryptów pozostaje taka sama, ale zamiast bezpośrednio włączać tekst mniejszych skryptów w głównym skrypcie kompilacji, SCons traktuje mniejsze skrypty jak osobne przestrzenie nazw (ponieważ SCons używa Pythona zamiast powłoki). EnvironmentObiekty SCons mają metodę o nazwie, SConscript()która umożliwia importowanie obiektów z SConstructpliku do plików pomocniczych zwanych SConscriptplikami, które mogą być użyte do refaktoryzacji skryptu kompilacji. Podstawową koncepcję SConscriptplików i SConscript()polecenia można znaleźć tutaj na Wiki SCons . Przykłady zastosowania SConscriptsw kompilacjach hierarchicznych można znaleźćtutaj w Podręczniku użytkownika SCons .

Ekstrapolując z tych trzech przykładów, wydaje się, że ogólną strategią jest refaktoryzacja skryptu kompilacji poprzez podzielenie go na skrypt główny, który wywołuje kilka plików. Jak to się robi, jest to idiomatyczne w stosunku do konkretnego oprogramowania do automatyzacji kompilacji.

Przykład SConsa, ponownie odwiedzony

Biorąc SConstructpowyższy plik, przeniosłem wszystkie informacje o konfiguracji do modułu o nazwie build_config.py. Wszystkie literały żyją w globalnej przestrzeni nazw, co może być niebezpieczne dla Pythona, ale można to łatwo (choć nieco nużąco) naprawić. Sprawdziłem z wyprzedzeniem, aby upewnić się, że nie mam żadnych konfliktów nazw __builtin__(wbudowana przestrzeń nazw Python, więc nie nadpisuję żadnych ważnych obiektów).

## \file build_config.py
#  \brief Sets configuration of file locations manually.
#

## Flags for compilers
#

flags = []

## Compile using debug versions?
#
debug = True
debugString = '-debug'
debugFlags = ['-ggdb']

dynamicLinkFlag = '-Wl,-rpath,'

if debug:
    flags += debugFlags

## Configuration information for GTest
#
GTestVersion = '1.6.0'
GTestStem = 'gtest-' + GTestVersion
GTestBuildIncDir = [GTestStem,
                    GTestStem + '/include',
                    ]

GTestIncDir = GTestStem + '/include/gtest'
GTestLibDir = 'lib'
GTestLibFlags = ['gtest', 'gtest_main', 'pthread']

## Configuration information for Armadillo matrix library
#

ArmadilloLibFlags = ['armadillo'];

## Locations of libraries installed on system in standard locations
#
StdIncDir = '/usr/include'
StdLibDir = '/usr/lib'

## Configuration information for COIN libraries
#
CoinUtilsVersion = '2.6.4'
ClpVersion = '1.12.0'
OsiVersion = '0.103.0'
CbcVersion = '2.5.0'

## Standard directory locations of COIN libraries, with slashes added for
#  for convenience.
#
CoinLibLocation = '/usr/local/COIN/'
StdCoinIncDir = '/include/coin'
StdCoinLibDir = '/lib'

CoinUtilsStem = 'CoinUtils-' + CoinUtilsVersion
ClpStem = 'Clp-' + ClpVersion
OsiStem = 'Osi-' + OsiVersion
CbcStem = 'Cbc-' + CbcVersion

if debug:
    CoinUtilsStem += debugString
    CbcStem += debugString
    ClpStem += debugString
    OsiStem += debugString

## Build up include directory names for COIN projects from constituent parts.
#
CoinUtilsIncDir = CoinLibLocation + CoinUtilsStem + StdCoinIncDir
ClpIncDir = CoinLibLocation + ClpStem + StdCoinIncDir
OsiIncDir = CoinLibLocation + OsiStem + StdCoinIncDir
CbcIncDir = CoinLibLocation + CbcStem + StdCoinIncDir

## Build up library names from COIN projects from constituent parts
#
CoinUtilsLibDir = CoinLibLocation + CoinUtilsStem + StdCoinLibDir
ClpLibDir = CoinLibLocation + ClpStem + StdCoinLibDir
OsiLibDir = CoinLibLocation + OsiStem + StdCoinLibDir
CbcLibDir = CoinLibLocation + CbcStem + StdCoinLibDir

## CPLEX
#
CpxStem = '/opt/ibm/ILOG/CPLEX_Studio_Academic123/cplex/'
CpxIncDir = CpxStem + 'include/ilcplex'
CpxLibDir = CpxStem + 'lib/x86-64_sles10_4.1/static_pic'

## Gurobi
# 
GrbStem = '/opt/gurobi460/linux64/'
GrbIncDir = GrbStem + 'include'
GrbLibDir = GrbStem + 'lib'

OsiLibFlags = ['Osi', 'CoinUtils']
ClpLibFlags = ['Clp', 'OsiClp']
CbcLibFlags = ['Cbc', 'Cgl']
OsiCpxLibFlags = ['OsiCpx']
OsiGrbLibFlags = ['OsiGrb']
CpxLibFlags = ['cplex', 'ilocplex', 'pthread', 'm']
GrbLibFlags = ['gurobi_c++', 'gurobi46', 'pthread', 'm']

milpIncDirs = [CoinUtilsIncDir,
               ClpIncDir,
               OsiIncDir,
               CbcIncDir,
               CpxIncDir,
               GrbIncDir,
            GTestIncDir,
               ]
milpLibDirs = [CoinUtilsLibDir,
               ClpLibDir,
               OsiLibDir,
               CbcLibDir,
               CpxLibDir,
               GrbLibDir,
               GTestLibDir,
            ]
milpLibFlags = [OsiCpxLibFlags,
                OsiGrbLibFlags,
                CbcLibFlags,
                ClpLibFlags,
                OsiLibFlags,
                CpxLibFlags,
                GrbLibFlags,
                GTestLibFlags,
                ]

## Configuration information for Chemkin source directories and files
#
ChemkinSourceDir = '/mnt/hgfs/DataFromOldLaptop/Data/ModelReductionResearch/Papers/AdaptiveChemistryPaper/AdaptiveChemistry/NonOpenSource/ChemkinII/';
ChemkinSourceList = ['cklib.f', 'pcmach.f','tranlib.f']
ChemkinSourceList = [ChemkinSourceDir + FileName
                     for FileName in ChemkinSourceList]

## Configuration information for Cantera
#
CanteraStem = '/usr/local/cantera'

if debug:
    CanteraStem += debugString

CanteraIncDir = CanteraStem + '/include/cantera'
CanteraLibDir = CanteraStem + '/lib'
CanteraTestingFlags = ['kinetics', 'thermo', 'tpx', 'ctbase', 'm',]
CanteraLibFlags = ['user', 'oneD', 'zeroD', 'equil', 'kinetics', 'transport',
                    'thermo', 'ctnumerics', 'ctmath', 'tpx', 'ctspectra',
                    'converters', 'ctbase', 'cvode', 'ctlapack', 'ctblas',
                    'ctf2c', 'ctcxx', 'ctf2c', 'm', 'm', 'stdc++']

CxxFortranFlags = ['g2c', 'gfortran']; 

chemSolverIncDir = [CanteraIncDir,
                    StdIncDir,
                    '/usr/local/include',
                    GTestIncDir,
                    ]
chemSolverLibDir = [StdLibDir,
                    CanteraLibDir,
                    GTestLibDir,
                    ]
chemSolverLibFlags = [GTestLibFlags,
                      CxxFortranFlags,
                      CanteraLibFlags,
                      ArmadilloLibFlags,
                      ]
canteraGTestLibFlags = CanteraTestingFlags + GTestLibFlags

#canteraGTestLibFlags = ['kinetics', 'thermo', 'tpx',
#                        'ctbase',  'm', 'gtest', 'gtest_main', 'pthread']

Główny SConstructplik wywołuje wiele SConscriptplików do budowy modułów zawierających główne funkcje, które mam w kodzie. Przeniesienie większości poleceń kompilacji do SConscriptplików sprawia, że SConstructplik jest naprawdę prosty:

## \file SConstruct
#  \brief Compiles the library and compiles tests.
#

import SCons
from build_config import *

## \brief Build up directory names of each COIN library from package names
#         and versions.
#

## Overall SCons environment
#
env = Environment();

## Compile Google Test from source using SConscript file.
#
GTestAllLib, GTestMainLib = env.SConscript('gtest.scons',
                                           exports=['env'])

## Compile MILP solver module and tests from source using SConscript file.
#
milpSolverTest = env.SConscript('milpSolver.scons',
                                exports=['env'])

## Compile chemistry solver module and associated tests from source
#  using SConscript file.
chemSolverTest, canteraGTest = env.SConscript('chemSolver.scons',
                                              exports=['env'])

## Since all tests use GTest, make the dependency of the module
# tests on the GTest libraries explicit.
env.Depends(milpSolverTest, [GTestAllLib, GTestMainLib])
env.Depends(chemSolverTest, [GTestAllLib, GTestMainLib])
env.Depends(canteraGTest, [GTestAllLib, GTestMainLib])

#env.AddPostAction(milpSolverTest, milpSolverTest[0].abspath)
testAlias = env.Alias('test', [milpSolverTest, chemSolverTest])
AlwaysBuild(testAlias)

Następnie wszystkie trzy SConscriptpliki mają rozszerzenie .sconsi dzielą projekt na moduły reprezentujące różne funkcje.

SConscriptPlik testowy Google :

## \file gtest.scons
#  \brief SConscript file that contains information for SCons build of
#  GTest. Use Python syntax highlighting for source.

from build_config import *
Import('env')

## Compile Google Test from scratch.
#
GTestAllLib = env.Library('lib/libgtest.a', 'gtest-1.6.0/src/gtest-all.cc',
                      CPPPATH = GTestBuildIncDir,
                      CXXFLAGS = flags)
GTestMainLib = env.Library('lib/libgtest_main.a',
                           'gtest-1.6.0/src/gtest_main.cc',
                           CPPPATH = GTestBuildIncDir,
                           CXXFLAGS = flags)

Return('GTestAllLib', 'GTestMainLib')

SConscriptPlik solvera programowania liniowego o mieszanych liczbach całkowitych :

## \file milpSolver.scons
#  \brief SConscript file that contains information for SCons build of
#  mixed-integer linear programming solver module. Use Python syntax
#  highlighting for source.

from build_config import *
Import('env')

## Compile MILP solver module and tests.
#

##milpSolver = env.Object('milpSolver.cpp',
            ##                         CPPPATH = milpIncDirs,
##                         LIBPATH = milpLibDirs,
##                         CXXFLAGS = flags)
milpSolverTest = env.Program('milpSolverUnitTest',
                                  ['milpSolverTest.cpp',
                                   'milpSolver.cpp'],
                                  CPPPATH = milpIncDirs,
                                  LIBPATH = milpLibDirs,
                                  LIBS = milpLibFlags,
                                  CXXFLAGS = flags,
                                  LINKFLAGS = ['-Wl,-rpath,' + OsiLibDir])

Return('milpSolverTest')

SConscriptPlik silnika chemii :

## \file chemSolver.scons
#  \brief  SConscript file that sets up SCons build of chemistry solver module.
#  Use Python syntax highlighting for source.

from build_config import *
Import('env')

## Compile CHEMKIN interpreter.
#
ckInterp = env.Program('ckinterp', ChemkinSourceDir + 'ckinterp.f')

## Enforce explicit dependence of CHEMKIN library on CHEMKIN
#  parameter file 'ckstrt.f' because SCons' scanner won't pick it up.
env.Depends('cklib.f','ckstrt.f')

chemSolverTest = env.Program('chemSolverUnitTest',
                        ['chemSolverTest.cpp',
                         'chemSolver.cpp',
                         'ckwrapper.f90'] + ChemkinSourceList,
                        CPPPATH = chemSolverIncDir,
                        LIBPATH = chemSolverLibDir,
                        LIBS = chemSolverLibFlags,
                        CXXFLAGS = flags,
                        FORTRANFLAGS = flags,
                        F90FLAGS = flags)

canteraGTest = env.Program('canteraGTest',
                           'canteraGTest.cpp',
                           CPPPATH = chemSolverIncDir,
                           LIBPATH = chemSolverLibDir,
                           LIBS = canteraGTestLibFlags,
                           CXXFLAGS = flags)

canteraMemTestLibFlags = CanteraTestingFlags

canteraMemTest = env.Program('canteraMemTest',
                             'canteraMemTest.cpp',
                             CPPPATH = chemSolverIncDir,
                             LIBPATH = chemSolverLibDir,
                             LIBS = canteraMemTestLibFlags,
                             CXXFLAGS = flags)

Return('chemSolverTest', 'canteraGTest')

Kombinacja tych pięciu plików jest prawdopodobnie nieco dłuższa niż oryginalny długi plik, ale łatwiej mi zarządzać, ponieważ mogę podzielić kompilację na plik konfiguracyjny i kilka przeważnie niepowiązanych jednostek, więc nie muszę śledzę cały proces w mojej głowie jednocześnie.

Geoff Oxberry
źródło
4

Są to funkcje, po prostu przestrzegają nieco innych zasad. „Funkcje” są często celami i zasadami budowania. Innym sposobem na zastanowienie się nad tym są węzły i linie w DAG kompilacji. „Celami” są węzły lub artefakty budowania, a linie to zasady przekształcania poprzednich węzłów.

Najpierw podchodzę do refaktoryzacji skryptów kompilacji, zastanawiając się, czym jest DAG. Następnie wyodrębnij wspólne reguły i usuń duplikaty.

Oto uproszczony przykład:

all:
   mkdir bigfiles
   cat file1 file2 > bigfiles/bigfile1
   cat file3 file4 > bigfiles/bigfile2

Czym powinien być DAG:

file1 \
        =-> bigfile1 \
file2 /               \
                        =-> all
file3 \               /
        =-> bigfile2 /
file4 /

Nowe zasady:

 bigfiles:
   mkdir bigfiles

 bigfiles/bigfile1: bigfiles
   cat file1 file2 > bigfiles/bigfile1

 bigfiles/bigfiles2: bigfiles
   cat file3 file4 > bigfiles/bigfile2

Zduplikowane:

 BIGFILES:=bigfiles/bigfile1 bigfiles/bigfile2
 bigfiles/bigfile1:=file1 file2
 bigfiles/bigfile2:=file3 file4

 .PHONY: all
 all: $(BIGFILES)

 bigfiles:
    mkdir bigfiles

 $(BIGFILES): bigfiles
    cat $($@) > $@

Pod koniec tego ćwiczenia mam obecnie nieco więcej kodu niż zacząłem. JEDNAK masz też bardziej ogólną „funkcję”. „Funkcja”, która została odpowiednio sparametryzowana i dzięki temu jest łatwiejsza w utrzymaniu i rozszerzalna.

dietbuddha
źródło
+1 za pomysł spojrzenia na DAG. Zgadzam się, że istnieją potencjalne możliwości refaktoryzacji. Wizualizacja DAG może być uciążliwa. W przypadku Makefiles istnieje Makefile :: GraphViz , a w przypadku SCons jest tutaj skrypt Pythona , który może wymagać nieco włamania, w zależności od projektu. Wykresy, które dał mi skrypt, były czasem niesamowicie duże, więc stwierdziłem, że musiałem dokładnie przefiltrować to, co chciałem wizualizować.
Geoff Oxberry
Nie musisz patrzeć na cały DAG. Możesz po prostu przejść przez plik Makefile pliku SConstruct i celować w najgorsze części. Zidentyfikuj, co robią, przerób niejawne DAG i wyłam je. Przenieś ogólne zasady do osobnych plików. Organizuj według danych / funkcji / celu z wysoką spójnością, tak jak w przypadku zwykłego języka programowania.
dietbuddha,
Tak, właśnie to próbowałem zrobić, gdy odfiltrowałem wiele plików, ponieważ parser zależności SConstruct pobiera ogromną liczbę plików. Więc odfiltrowałem je i użyłem skryptu Python pod linkiem, aby utworzyć plik kropkowy, aby móc wizualizować części DAG, które moim zdaniem były ważne. Udało się, a dostałem wystarczająco mały wykres, aby móc wybrać to, co było ważne, ale nie widziałem zbyt wiele, by z niego skorzystać. Podejrzewam, że ta strategia jest znacznie łatwiejsza w użyciu w Make, ponieważ łatwiej jest znaleźć powtarzające się reguły w Makefiles, jak widać ze źródła, które opublikowałem.
Geoff Oxberry
2

GNU autoconf został stworzony, aby pliki makefile były łatwe w utrzymaniu i przenośne, poprzez przeniesienie logiki do osobnego skryptu.

Rozważę użycie tego jako pierwszego kroku, który możesz zrobić, aby zarządzać plikami Makefiles.


źródło
Zgadzam się, że GNU Autoconf został stworzony z myślą o przenośności i konserwacji kompilacji. W pewnym momencie zbadałem naukę Autoconf i odkryłem, że zajmie to więcej czasu, niż chciałbym zainwestować. (Kończę doktorat, więc zbudowanie tego oprogramowania musi nastąpić wcześniej niż później.) Być może w przyszłości? Przenośność nie jest nawet dla mnie głównym problemem, to długość i brak modułowości w skryptach kompilacji i nie jestem pewien, czy przeniesienie do Autoconf rozwiąże te problemy.
Geoff Oxberry
Następnie zastanów się nad pokazaniem niektórych rzeczywistych problemów i dlaczego obecne rozwiązanie nie jest wystarczająco dobre. Ważne jest także, aby miało to miejsce w środowisku akademickim.
Dodałem przykłady ilustrujące to, nad czym teraz pracuję.
Geoff Oxberry