มีวิธีใดที่ดีในการจัดระเบียบไฟล์อินพุต (Makefiles, SConstruct, CMakeLists.txt และอื่น ๆ ) เพื่อสร้างซอฟต์แวร์อัตโนมัติ


13

สิ่งหนึ่งที่ฉันต้องการทำกับรหัสของฉันคือการทำให้แน่ใจว่ามัน refactored เป็นชิ้นจัดการ อย่างไรก็ตามเมื่อมันมาถึงการสร้างซอฟต์แวร์ฉันพบว่าสิ่งที่สร้างซอฟต์แวร์อัตโนมัติที่ฉันใช้ (เมื่อเร็ว ๆ นี้มันเป็น GNU Make หรือ SCons) กลายเป็นความยุ่งเหยิงอย่างสมบูรณ์ ไฟล์อินพุตมีลักษณะเหมือนสคริปต์แบบยาวที่ดูเหมือนจะท้าทายการปรับโครงสร้างใหม่ได้ง่าย ฉันต้องการที่จะ refactor พวกเขาในทางใดทางหนึ่ง แต่แนวคิดของ "ฟังก์ชั่น" ไม่ได้ทำงานในลักษณะเดียวกันในซอฟต์แวร์สร้างอัตโนมัติบางอย่างเช่นในภาษาการเขียนโปรแกรมดังนั้นฉันคิดว่ามันยากที่จะเขียนจัดการได้ ไฟล์ Makefiles หรือ SConscript สำหรับโครงการใด ๆ ที่มีความซับซ้อนปานกลาง

ไม่มีใครมีคำแนะนำในการเขียนไฟล์อินพุตที่สามารถจัดการได้สำหรับซอฟต์แวร์สร้างอัตโนมัติ คำแนะนำผู้ไม่เชื่อเรื่องพระเจ้าของซอฟต์แวร์จะดีที่สุด แต่แม้คำแนะนำเกี่ยวกับเครื่องมือสร้างอัตโนมัติเฉพาะจะมีประโยชน์โดยเฉพาะอย่างยิ่ง Make หรือ SCons เนื่องจากนั่นคือสิ่งที่ฉันใช้สำหรับโครงการ

แก้ไข:ตามที่Thorbjørnชี้ให้เห็นฉันควรเพิ่มบริบทและตัวอย่างการใช้งาน ฉันจบปริญญาเอกสาขาวิศวกรรมเคมีและฉันค้นคว้าทางวิทยาศาสตร์การคำนวณ (ฉันเป็นนักเขียนมืออาชีพที่ SciComp.SE สำหรับผู้ที่เข้าชม) โดยทั่วไปโครงการของฉันเกี่ยวข้องกับภาษาที่รวบรวม (C, C ++, Fortran) ที่มีการยกระดับสูง, สคริปต์ภาษา (Python) , Perl) สำหรับการสร้างต้นแบบและบางครั้งภาษาเฉพาะโดเมนสำหรับการสร้างต้นแบบหรือวัตถุประสงค์ทางเทคนิค

ฉันได้เพิ่มสองตัวอย่างด้านล่างโดยประมาณในช่วง 250 บรรทัด ปัญหาสำหรับฉันโดยทั่วไปคือการขาดแบบแยกส่วน บางส่วนของโครงการเหล่านี้สามารถจัดระเบียบเป็นหน่วยแบบแยกส่วนและมันเป็นเรื่องดีที่จะสรุปส่วนต่างๆของงานสร้างตามแนวเส้นเหล่านั้นเพื่อให้ง่ายขึ้นสำหรับฉันและผู้ดูแลในอนาคตในการติดตาม การแบ่งแต่ละสคริปต์ออกเป็นหลายไฟล์เป็นทางออกหนึ่งที่ฉันได้ลองเล่นด้วย

ตัวอย่างที่สองมีความสำคัญอย่างยิ่งเพราะฉันจะต้องใช้ไฟล์จำนวนมากในไม่ช้า

นี่คือสิ่งที่ 265-line Makefileอาจดูเหมือนสำหรับฉันนำมาจากโครงการจริงและจัดระเบียบอย่างดีที่สุดที่ฉันสามารถทำได้:

#!/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

นี่คือSConstructไฟล์245 บรรทัดที่ฉันกำลังพยายามจัดระเบียบสำหรับโครงการที่มีความซับซ้อน:

## \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)

คำตอบ:


8

กลยุทธ์การปรับโครงสร้างใหม่

นอกจากความคิดเห็นโดย dietbuddha และThorbjørnRavnAndersonอีกวิธีหนึ่งในการสร้างสคริปต์การสร้างใหม่ก็คือการแยกสคริปต์เหล่านั้นออกเป็นหลายไฟล์ วิธีที่คุณทำสิ่งนี้ขึ้นอยู่กับระบบบิลด์

สำหรับทำก็ง่ายเหมือนการใช้includeคำสั่งตามที่แนะนำใน"ทำให้ซ้ำพิจารณาอันตราย" คำสั่งนี้ทำงานเหมือนกับ#includeในตัวประมวลผลล่วงหน้า C และประมวลผลไฟล์ที่รวมไว้ราวกับว่ามันถูกตัดและวางแทนincludeคำสั่ง การใช้includeคำสั่งมันเป็นไปได้ที่จะ refactor หลักของคุณMakefileโดยการย้ายชิ้น modular เข้าไปย่อยMakefiles

CMake มีคำสั่งที่คล้ายกัน

SCons ต้องการวิธีการที่คล้ายกันโดยมีคำสั่งต่าง แนวคิดพื้นฐานของการแยกสคริปต์บิลด์ให้เป็นสคริปต์หลักและสคริปต์ย่อยขนาดเล็กหลาย ๆ ตัวจะยังคงเหมือนเดิม แต่แทนที่จะรวมถึงข้อความของสคริปต์ที่เล็กกว่าในสคริปต์สร้างต้นแบบ SCons จะใช้สคริปต์ขนาดเล็กเช่นเนมสเปซแยกต่างหาก SCons ใช้ Python แทนเชลล์) EnvironmentวัตถุSCons มีวิธีการที่เรียกSConscript()ว่าช่วยให้คุณสามารถนำเข้าวัตถุจากSConstructไฟล์ไปยังไฟล์ในเครือที่เรียกว่าSConscriptไฟล์ที่สามารถใช้ในการ refactor สคริปต์สร้างของคุณ แนวคิดพื้นฐานของSConscriptไฟล์และSConscript()คำสั่งที่สามารถพบได้ที่นี่ในscons วิกิพีเดีย ตัวอย่างของวิธีใช้SConscriptsในบิลด์แบบลำดับชั้นสามารถพบได้ที่นี่ในคู่มือการใช้ scons

การคาดการณ์จากตัวอย่างทั้งสามนี้ดูเหมือนว่ากลยุทธ์ทั่วไปคือการสร้างสคริปต์การสร้างใหม่โดยแยกออกเป็นสคริปต์หลักที่เรียกหลายไฟล์ วิธีนี้เป็นวิธีสำนวนซอฟต์แวร์สร้างอัตโนมัติที่ใช้

ตัวอย่าง SCons, Revisited

เอาไฟล์ข้างต้นผมย้ายทั้งหมดของข้อมูลการกำหนดค่าลงในโมดูลที่เรียกว่าSConstruct build_config.pyตัวอักษรทั้งหมดอยู่ในเนมสเปซส่วนกลางซึ่งอาจเป็นอันตรายสำหรับ Python แต่นั่นอาจแก้ไขได้ง่าย (แต่ค่อนข้างน่าเบื่อ) ฉันตรวจสอบล่วงหน้าเพื่อให้แน่ใจว่าฉันไม่มีชื่อที่ขัดแย้งกัน__builtin__(Python ในตัวของ Python ดังนั้นฉันจึงไม่เขียนทับวัตถุสำคัญใด ๆ )

## \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']

SConstructไฟล์หลักเรียกSConscriptไฟล์จำนวนมากเพื่อสร้างโมดูลที่มีฟีเจอร์หลักที่ฉันมีในโค้ดของฉัน การย้ายคำสั่ง build ไปยังSConscriptไฟล์ส่วนใหญ่ทำให้SConstructไฟล์นั้นง่ายมาก:

## \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)

จากนั้นSConscriptไฟล์ทั้งสามมีส่วนขยาย.sconsและแยกโครงการเป็นโมดูลที่แสดงถึงการทำงานที่แตกต่างกัน

SConscriptไฟล์Google Test :

## \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')

SConscriptไฟล์ตัวแก้ปัญหาการโปรแกรมเชิงเส้นแบบผสมจำนวนเต็ม:

## \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')

SConscriptไฟล์เครื่องยนต์เคมี:

## \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')

การรวมกันของห้าไฟล์นี้น่าจะนานกว่าไฟล์ยาวดั้งเดิมนิดหน่อย แต่มันง่ายกว่าสำหรับฉันที่จะจัดการเพราะฉันสามารถแยกบิลด์ออกเป็นไฟล์กำหนดค่าได้ ติดตามการสร้างทั้งหมดในใจของฉันในครั้งเดียว


4

พวกมันคือฟังก์ชั่นพวกเขาแค่ทำตามกฎต่างกันเล็กน้อย "ฟังก์ชั่น" มักเป็นเป้าหมายและกฎสำหรับการสร้าง วิธีคิดอีกอย่างคือโหนดและเส้นใน build DAG "เป้าหมาย" คือโหนดหรือสร้างสิ่งประดิษฐ์และบรรทัดเป็นกฎเกี่ยวกับวิธีการแปลงโหนดก่อนหน้า

ฉันเข้าใกล้การสร้างสคริปต์ใหม่อีกครั้งโดยการหาว่า DAG คืออะไร จากนั้นแยกกฎทั่วไปและยกเลิกการทำซ้ำ

นี่คือตัวอย่างง่ายๆ

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

DAG ควรเป็นอย่างไร:

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

กฎใหม่:

 bigfiles:
   mkdir bigfiles

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

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

De ซ้ำ:

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

 .PHONY: all
 all: $(BIGFILES)

 bigfiles:
    mkdir bigfiles

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

ในตอนท้ายของแบบฝึกหัดนี้ฉันมีรหัสมากกว่าที่ฉันเริ่มต้นเล็กน้อย อย่างไรก็ตามคุณมี "ฟังก์ชัน" ทั่วไปมากขึ้น "ฟังก์ชั่น" ที่ได้รับการ parametrized อย่างถูกต้องและทำให้สามารถบำรุงรักษาและยืดออกได้มากขึ้น


+1 สำหรับแนวคิดในการดู DAG ฉันยอมรับว่ามีโอกาสที่จะทำให้เกิดข้อผิดพลาดอีกครั้ง การมองภาพ DAG อาจเป็นความเจ็บปวดในตูด สำหรับ Makefiles นั้นมีMakefile :: GraphVizและสำหรับ SCons จะมีสคริปต์ Python ที่นี่ซึ่งอาจต้องมีการแฮ็กเล็กน้อยขึ้นอยู่กับโครงการ กราฟที่สคริปต์ให้มาบางครั้งก็มีขนาดใหญ่มากดังนั้นฉันจึงพบว่าฉันต้องกรองสิ่งที่ฉันต้องการให้เห็นอย่างละเอียด
Geoff Oxberry

คุณไม่จำเป็นต้องดู DAG ทั้งหมด คุณสามารถผ่านไฟล์ Makefile of SConstruct และกำหนดเป้าหมายส่วนที่แย่ที่สุด ระบุสิ่งที่พวกเขากำลังทำใหม่ในการทำ DAG โดยปริยายและแยกออก ย้ายกฎทั่วไปเป็นไฟล์แยก จัดระเบียบตามข้อมูล / ฟังก์ชั่น / เป้าหมายที่มีการติดต่อกันสูงเช่นเดียวกับภาษาโปรแกรมปกติ
dietbuddha

ใช่นั่นคือสิ่งที่ฉันพยายามทำเมื่อฉันกรองไฟล์จำนวนมากออกเนื่องจากตัวแยกวิเคราะห์ SConstruct พึ่งพาพึ่งพาหยิบไฟล์จำนวนมาก ดังนั้นฉันจึงกรองสิ่งเหล่านั้นออกและใช้สคริปต์ Python ที่ลิงก์เพื่อสร้าง dotfile เพื่อให้ฉันสามารถเห็นภาพส่วนต่างๆของ DAG ที่ฉันคิดว่ามีความสำคัญ มันใช้งานได้และฉันมีกราฟขนาดเล็กพอที่จะเลือกสิ่งที่สำคัญ แต่ฉันไม่เห็นเลยว่ามีอะไรให้ใช้มากมาย ฉันสงสัยว่ากลยุทธ์นี้ใช้งานได้ง่ายกว่ามากกับ Make เนื่องจากง่ายต่อการค้นหากฎที่ซ้ำกันใน Makefiles อย่างที่คุณเห็นจากแหล่งที่ฉันโพสต์
Geoff Oxberry

2

GNU autoconf สร้างขึ้นเพื่อให้สามารถบำรุงรักษาและพกพาได้โดยย้ายตรรกะไปที่สคริปต์ที่แยกต่างหาก

ฉันจะพิจารณาว่าเป็นขั้นตอนแรกที่คุณสามารถทำได้เพื่อจัดการ Makefiles ของคุณ


ฉันยอมรับว่า GNU Autoconf สร้างขึ้นเพื่อความสะดวกในการพกพาและการดูแลรักษาของการสร้าง จนถึงจุดหนึ่งฉันตรวจสอบการเรียนรู้ Autoconf และพบว่ามันจะต้องใช้เวลามากกว่าที่ฉันยินดีลงทุน (ฉันจบปริญญาเอกของฉันดังนั้นการสร้างซอฟต์แวร์นี้จะต้องเกิดขึ้นเร็วกว่าในภายหลัง) บางทีในอนาคต? การพกพาไม่ได้เป็นปัญหาหลักสำหรับฉันมันคือความยาวและการขาดโมดูลในการสร้างสคริปต์และฉันไม่แน่ใจว่าการย้ายไปที่ Autoconf จะช่วยแก้ปัญหาเหล่านั้นได้
Geoff Oxberry

จากนั้นลองพิจารณาแสดงปัญหาที่แท้จริงของคุณและทำไมการแก้ปัญหาปัจจุบันของคุณจึงไม่ดีพอ นอกจากนี้มันเป็นสิ่งสำคัญจริง ๆ ที่เป็นในเชิงวิชาการ

ฉันได้เพิ่มตัวอย่างเพื่ออธิบายสิ่งที่ฉันทำงานด้วยตอนนี้
Geoff Oxberry
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.