অটোমেশন সফ্টওয়্যার তৈরির জন্য ইনপুট ফাইলগুলি (মেকফাইলস, স্ক্যানস্ট্রাক্ট, সিএমকেলিস্ট.টেক্সট ইত্যাদি) ভাল করার উপায়গুলি কী কী?


13

আমার কোডটি দিয়ে আমি যে জিনিসটি করতে চাই তা হ'ল এটি পরিচালনাযোগ্য টুকরোতে পুনরুদ্ধার করা উচিত তা নিশ্চিত করুন। তবে, সফ্টওয়্যারটি তৈরির ক্ষেত্রে, আমি দেখতে পেলাম যে অটোমেশন সফ্টওয়্যারটি ব্যবহার করে আমি শেষ করি না কেন (ইদানীং এটি জিএনইউ মেক বা এসসিএনস হয়েছে) সম্পূর্ণ গণ্ডগোল হয়ে ওঠে। ইনপুট ফাইলগুলি দীর্ঘ স্ক্রিপ্টগুলির মতো দেখায় যা মনে হয় সহজে রিপ্যাক্টরিংকে অস্বীকার করে। আমি তাদের কোনও উপায়ে রিফ্যাক্টর করতে সক্ষম হতে চাই, তবে "ফাংশন" ধারণাটি কোনও বিল্ড অটোমেশন সফ্টওয়্যার যেমন প্রোগ্রামিং ভাষায় করা হয় তেমনভাবে আচরণ করে না, তাই আমার পক্ষে ম্যানেজযোগ্য লিখতে অসুবিধা হয় মাঝারিভাবে জটিল যে কোনও প্রকল্পের জন্য মেকফাইলস বা স্কোনস্ক্রিপ্ট ফাইল।

বিল্ড অটোমেশন সফ্টওয়্যারটির ব্যবস্থাপনযোগ্য ইনপুট ফাইলগুলি লেখার বিষয়ে কারও কোনও পরামর্শ আছে? সফ্টওয়্যার-অজোনস্টিক পরামর্শ সবচেয়ে ভাল হবে তবে নির্দিষ্ট বিল্ড অটোমেশন সরঞ্জাম সম্পর্কেও পরামর্শ সহায়ক হবে, বিশেষত মেক বা এসসিএনস, যেহেতু আমি প্রকল্পগুলির জন্যই ব্যবহার করছি।

সম্পাদনা করুন: থোরবজার্ন যেমন উল্লেখ করেছেন, আমার কিছু প্রসঙ্গ এবং ব্যবহারের উদাহরণ যুক্ত করা উচিত। আমি রাসায়নিক ইঞ্জিনিয়ারিংয়ে আমার পিএইচডি শেষ করছি এবং আমি গণনা বিজ্ঞানে গবেষণা করি research (আপনারা যারা আপনারা দেখেন তাদের জন্য আমি সাইকম্প্প.এস.ই. তে একটি প্রো টেম মোড) আমার প্রকল্পগুলিতে সাধারণত সংকলিত ভাষার (সি, সি ++, ফোর্টরান) মিশ্রণ থাকে যা ভারী উত্তোলন, স্ক্রিপ্টিং ভাষাগুলির (পাইথন) কিছু করে , পার্ল) প্রোটোটাইপিং এর জন্য এবং এবং মাঝে মাঝে প্রোটোটাইপিং বা প্রযুক্তিগত উদ্দেশ্যে ডোমেন-নির্দিষ্ট ভাষা।

আমি নীচে দুটি উদাহরণ যুক্ত করেছি, প্রায় 250-লাইনের সীমার মধ্যে। আমার জন্য সমস্যাটি সাধারণত মডুলারালটির অভাব। এর মধ্যে কয়েকটি প্রকল্পকে মডুলার ইউনিটগুলিতে সংগঠিত করা যেতে পারে এবং আমার এবং ভবিষ্যতের রক্ষণাবেক্ষণকারীদের নজর রাখা সহজ করার জন্য এই লাইনের সাথে বিল্ডের অংশগুলি বিমূর্ত করা ভাল লাগবে। প্রতিটি স্ক্রিপ্টকে একাধিক ফাইলে ভাঙ্গা হ'ল এটি আমার সমাধানের সাথে ঘুরে বেড়াতে চলেছে solution

দ্বিতীয় উদাহরণটি বিশেষভাবে গুরুত্বপূর্ণ, কারণ আমি শিগগিরই এটির একটি বিশাল সংখ্যক ফাইল রাখব।

Makefileসত্যিকারের প্রকল্প থেকে নেওয়া এবং আমার পক্ষে সর্বোত্তমভাবে সংগঠিত এমন 265-লাইন আমার মতো দেখতে দেখতে এখানে কী :

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

এখানে একটি 245-লাইন SConstructফাইল যা আমি বর্তমানে প্রায় একটি জটিল হিসাবে একটি প্রকল্পের জন্য সংগঠিত করার চেষ্টা করছি:

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

রিফ্যাক্টরিং কৌশল

ডায়েটবুদ্ধা এবং থরবজর্নআরনঅ্যান্ডারসনের মন্তব্য ছাড়াও, রিফ্যাক্টর নির্মাণের স্ক্রিপ্টগুলির আরেকটি উপায় হ'ল এগুলি একাধিক ফাইলে আলাদা করা। আপনি এটি কীভাবে করবেন তা নির্ভর করে বিল্ড সিস্টেমের উপর।

মেকের জন্য, এটি includeকমান্ডটি ব্যবহার করার মতোই সহজ , "রিকার্সিভ মেক বিবেচিত ক্ষতিকারক" হিসাবে প্রস্তাবিত । এই নির্দেশটি ঠিক #includeসি প্রিপ্রোসেসরের মতোই কাজ করে এবং অন্তর্ভুক্ত ফাইলটিকে প্রক্রিয়াজাত করে যেন এটি includeকমান্ডের জায়গায় কাটা এবং আটকানো হয়েছিল । includeকমান্ডটি ব্যবহার করে , Makefileমডিউলার টুকরোগুলিকে সাব- এসগুলিতে স্থানান্তরিত করে আপনার মেইনটিকে রিফ্যাক্টর করা সম্ভব Makefile

সিএমকেও একই রকম কমান্ড রয়েছে।

বিভিন্ন কমান্ড সহ স্ক্যানদের একই ধরণের পদ্ধতির প্রয়োজন। বিল্ড স্ক্রিপ্টকে একটি মাস্টার স্ক্রিপ্টে বিভক্ত করার প্রাথমিক ধারণা এবং বেশ কয়েকটি ছোট ছোট সাব-স্ক্রিপ্ট একই থাকে, তবে মাস্টার বিল্ড স্ক্রিপ্টে ছোট স্ক্রিপ্টগুলির পাঠ্যকে সরাসরি অন্তর্ভুক্ত করার পরিবর্তে স্ক্যানস পৃথক নেমস্পেসের মতো ছোট স্ক্রিপ্টগুলির সাথে আচরণ করে (কারণ) স্ক্যানস শেলের পরিবর্তে পাইথন ব্যবহার করে)। এসসিএন Environmentঅবজেক্টগুলির একটি নামক পদ্ধতি রয়েছে যা SConscript()আপনাকে একটি বিল্ড স্ক্রিপ্টটি রিফেক্টর হিসাবে ব্যবহার করা যেতে পারে এমন SConstructফাইল নামক SConscriptফাইলগুলি সাবসিডিয়ারি ফাইলগুলিতে একটি ফাইল থেকে অবজেক্টগুলি আমদানি করতে সক্ষম করে । মৌলিক ধারণা SConscriptফাইল এবং SConscript()কমান্ড খুঁজে পাওয়া যেতে পারে এখানে উপর SCons উইকিSConscriptsশ্রেণিবদ্ধ বিল্ডগুলিতে কীভাবে ব্যবহার করবেন তার উদাহরণগুলি পাওয়া যায়এখানে মধ্যে SCons ইউজার গাইড

এই তিনটি উদাহরণ থেকে এক্সট্রোপোলটিং করে মনে হচ্ছে সাধারণ কৌশলটি কোনও বিল্ড স্ক্রিপ্টকে মাস্টার স্ক্রিপ্টে বিভক্ত করে বিভিন্ন ফাইলকে কল করে এটি রিফ্যাক্টর করা। এটি কীভাবে ব্যবহৃত হয় তা ব্যবহৃত নির্দিষ্ট বিল্ড অটোমেশন সফ্টওয়্যারটির কাছে মূর্তিমান।

স্ক্যানস উদাহরণ, পুনর্বিবেচিত

টেকিং SConstructউপরে ফাইল, আমি একটি মডিউল নামক কনফিগারেশন সমস্ত তথ্য সরানো build_config.py। সমস্ত আক্ষরিক গ্লোবাল নেমস্পেসে বাস করে যা পাইথনের পক্ষে বিপজ্জনক হতে পারে তবে এটি সহজেই (যদিও কিছুটা ক্লান্তিকরভাবে) স্থিরযোগ্য। আমি __builtin__(পাইথন অন্তর্নির্মিত নেমস্পেসের সাথে কোনও নাম সংঘর্ষ নেই) তা নিশ্চিত করার জন্য আমি সামনে যাচাই করেছিলাম , সুতরাং আমি কোনও গুরুত্বপূর্ণ অবজেক্টে ওভাররাইট করছি না)।

## \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। বিল্ড কমান্ডগুলির বেশিরভাগ 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ফাইল:

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

এগুলি ফাংশন, তারা কিছুটা আলাদা বিধি অনুসরণ করে। "ফাংশনগুলি" প্রায়শই বিল্ডিংয়ের জন্য লক্ষ্য এবং নিয়ম। এটি সম্পর্কে চিন্তা করার আর একটি উপায় হ'ল বিল্ড ডিএজি-র নোড এবং লাইন। "টার্গেটস" হ'ল নোড বা বিল্ডিং আর্টিফিকেস এবং লাইনগুলি পূর্ববর্তী নোডগুলিকে কীভাবে রূপান্তর করতে হয় তার নিয়ম।

আমি প্রথমে ডিএজি কী তা নির্ণয় করে বিল্ড স্ক্রিপ্টগুলি রিফ্যাক্টরিংয়ের কাছে যাই। তারপরে সাধারণ নিয়মগুলি বিচ্ছিন্ন করে ডি-সদৃশ করুন।

এখানে সরল উদাহরণ রয়েছে:

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

ডিএজি কী হওয়া উচিত:

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

ডি-সদৃশ:

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

 .PHONY: all
 all: $(BIGFILES)

 bigfiles:
    mkdir bigfiles

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

এই অনুশীলনের শেষে আমার কাছে শুরু করার চেয়ে আমার কাছে বর্তমানে কিছুটা বেশি কোড রয়েছে। তবুও, আপনার আরও একটি জেনেরিক "ফাংশন" রয়েছে। একটি "ফাংশন" যা সঠিকভাবে প্যারামিট্রাইজ করা হয়েছে এবং ফলস্বরূপ এটি আরও রক্ষণাবেক্ষণযোগ্য এবং এক্সটেনসিবল।


ড্যাগের দিকে তাকানোর ধারণার জন্য +1 করুন। আমি একমত যে সেখানে রিফ্যাক্টর সম্ভাব্য সুযোগ আছে। ডিএজি ভিজ্যুয়ালাইজ করা পাছায় ব্যথা হতে পারে। ফাইল তৈরী, সেখানে এর Makefile নামক :: Graphviz এবং SCons জন্য, একটি পাইথন স্ক্রিপ্টের এখানে যে, হ্যাকিং প্রকল্পের উপর নির্ভর করে একটি বিট প্রয়োজন হতে পারে। স্ক্রিপ্টটি আমাকে যে গ্রাফগুলি দিয়েছিল তা কখনও কখনও গুরুতর আকার ধারণ করে, তাই আমি দেখতে পেয়েছিলাম যে আমি কী ভিজ্যুয়ালাইজ করতে চেয়েছিলাম তা যত্ন সহকারে ফিল্টার করতে হয়েছিল।
জিওফ অক্সবেরি

আপনাকে পুরো ডিএজি দেখার দরকার নেই। আপনি কেবলমাত্র মেকফিল অফ এসিসনস্ট্রাক্ট ফাইলটি দিয়ে যেতে পারেন এবং সবচেয়ে খারাপ অংশগুলিকে লক্ষ্য করতে পারেন। তারা কী করছে তা সনাক্ত করুন অন্তর্নিহিত ডিএজি পুনরায় কাজ করুন এবং এটি ভেঙে ফেলুন। সাধারণ নিয়মগুলি পৃথক ফাইলে সরান। আপনি নিয়মিত প্রোগ্রামিং ভাষার সাথে যেমন উচ্চ সংহতি দিয়ে ডেটা / ফাংশন / লক্ষ্য অনুসারে সংগঠিত করুন।
ডায়েটবুদ্ধ

হ্যাঁ, আমি প্রচুর ফাইল ফিল্টার করার সময় এটি করার চেষ্টা করছিলাম, কারণ স্ক্যানস্ট্রাক্ট নির্ভরতা পার্সার বিপুল সংখ্যক ফাইল বাছাই করে। সুতরাং আমি সেগুলি ফিল্টার করেছি এবং ডটফাইল তৈরি করার জন্য লিঙ্কে পাইথন স্ক্রিপ্টটি ব্যবহার করেছি যাতে আমি যে ড্যাগের গুরুত্বপূর্ণ বিষয়গুলি ভেবেছিলাম তা কল্পনা করতে পারি। এটি কাজ করেছে, এবং আমি একটি ছোট যথেষ্ট গ্রাফ পেয়েছি যাতে আমি কী গুরুত্বপূর্ণ তা বেছে নিতে পারি, তবে আমি সেখানে পুরোটা ব্যবহার করতে পারি নি। আমি সন্দেহ করি যে এই কৌশলটি মেকের সাথে ব্যবহার করা অনেক সহজ কারণ মেকফাইলে বারবার নিয়মগুলি খুঁজে পাওয়া সহজ, আপনি পোস্ট করা উত্স থেকে আপনি দেখতে পাচ্ছেন।
জিফ অক্সবেরি

2

জিএনইউ অটোকনফকে যুক্তিগুলি একটি পৃথক স্ক্রিপ্টে সরিয়ে মেকফিলগুলি রক্ষণাবেক্ষণযোগ্য এবং পোর্টেবল করার জন্য তৈরি করা হয়েছিল।

আমি আপনার মেকফিলগুলি পরিচালনা করতে প্রথম পদক্ষেপ নিতে পারে তা ব্যবহার করে বিবেচনা করব।


আমি সম্মত হই যে জিএনইউ অটোকনফ বিল্ডের বহনযোগ্যতা এবং রক্ষণাবেক্ষণের জন্য তৈরি হয়েছিল। এক পর্যায়ে, আমি অটোকনফ শিখতে তদন্ত করেছি এবং দেখেছি যে এটি বিনিয়োগ করতে আমার ইচ্ছুক চেয়ে বেশি সময় নিবে। (আমি আমার পিএইচডি শেষ করছি, সুতরাং এই সফ্টওয়্যারটি তৈরি করার পরে খুব শীঘ্রই হওয়া উচিত needs) সম্ভবত ভবিষ্যতে? বহনযোগ্যতা আমার পক্ষে এমনকি মূল সমস্যা নয়, এটি বিল্ড স্ক্রিপ্টগুলির দৈর্ঘ্য এবং মডুলারালটির অভাব এবং আমি নিশ্চিত নই যে অটোকনফের পোর্টিংগুলি এই সমস্যাগুলি সমাধান করবে।
জিফ অক্সবেরি

তারপরে আপনার আসল সমস্যাগুলির কয়েকটি দেখাচ্ছে এবং কেন আপনার বর্তমান সমাধান যথেষ্ট ভাল নয় তা বিবেচনা করুন। এছাড়াও, এটি বাস্তবে গুরুত্বপূর্ণ যে এটি একাডেমিতে ia

আমি এই মুহুর্তে কী নিয়ে কাজ করছি তা বোঝাতে উদাহরণ যুক্ত করেছি।
জেফ অক্সবেরি
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.