xref: /petsc/config/BuildSystem/config/packages/make.py (revision 4d1dc6774962d89e027de0ec27b662257ea5bd85)
1import config.package
2import os
3
4def getMakeUserPath(arch):
5  import re
6  file = os.path.join(arch, 'lib', 'petsc', 'conf', 'petscvariables')
7  try:
8    with open(file, 'r') as f:
9      return next(line for line in f if re.match(r'\AMAKE_USER\s*=',line)).split('=')[1].strip()
10  except:
11    return 'make'
12
13class Configure(config.package.GNUPackage):
14  def __init__(self, framework):
15    config.package.GNUPackage.__init__(self, framework)
16    self.minversion        = '3.81'
17    self.download          = ['https://ftp.gnu.org/gnu/make/make-4.4.1.tar.gz',
18                              'https://web.cels.anl.gov/projects/petsc/download/externalpackages/make-4.4.1.tar.gz']
19    self.downloadonWindows = 1
20    self.useddirectly      = 0
21    self.linkedbypetsc     = 0
22    self.printdirflag      = ''
23    self.noprintdirflag    = ''
24    self.paroutflg         = ''
25    self.shuffleflg        = ''
26    self.publicInstall     = 0  # always install in PETSC_DIR/PETSC_ARCH (not --prefix) since this is not used by users
27    self.parallelMake      = 0
28    self.skippackagelibincludedirs = 1
29    self.executablename    = 'make'
30    self.skipMPIDependency = 1
31    return
32
33  def setupHelp(self, help):
34    import nargs
35    config.package.GNUPackage.setupHelp(self, help)
36    help.addArgument('MAKE', '-with-make-np=<np>',                           nargs.ArgInt(None, None, min=1, help='Default number of processes to use for parallel builds'))
37    help.addArgument('MAKE', '-with-make-test-np=<np>',                      nargs.ArgInt(None, None, min=1, help='Default number of processes to use for parallel tests'))
38    help.addArgument('MAKE', '-with-make-load=<load>',                       nargs.ArgReal(None, None, min=1.0, help='max load to use for parallel builds'))
39    help.addArgument('MAKE', '-download-make-cc=<prog>',                     nargs.Arg(None, None, 'C compiler for GNU make configure'))
40    help.addArgument('MAKE', '-with-make-exec=<executable>',                 nargs.Arg(None, None, 'Make executable to look for'))
41    return
42
43  def formGNUConfigureArgs(self):
44    '''Does not use the standard arguments at all since this does not use the MPI compilers etc
45       Sowing will chose its own compilers if they are not provided explicitly here'''
46    args = ['--prefix='+self.installDir]
47    args.append('--without-guile')
48    if 'download-make-cc' in self.argDB and self.argDB['download-make-cc']:
49      args.append('CC="'+self.argDB['download-make-cc']+'"')
50    return args
51
52  def Install(self):
53    ''' Cannot use GNUPackage Install because that one uses make which does not yet exist
54        This is almost a copy of GNUPackage Install just avoiding the use of make'''
55    args = self.formGNUConfigureArgs()
56    args = ' '.join(args)
57    conffile = os.path.join(self.packageDir,self.package+'.petscconf')
58    fd = open(conffile, 'w')
59    fd.write(args)
60    fd.close()
61    ### Use conffile to check whether a reconfigure/rebuild is required
62    if not self.installNeeded(conffile):
63      return self.installDir
64    ### Configure and Build package
65    try:
66      self.logPrintBox('Running configure on ' +self.PACKAGE+'; this may take several minutes')
67      output1,err1,ret1  = config.base.Configure.executeShellCommand('cd '+self.packageDir+' && ./configure '+args, timeout=2000, log = self.log)
68    except RuntimeError as e:
69      raise RuntimeError('Error running configure on ' + self.PACKAGE+': '+str(e))
70    try:
71      self.logPrintBox('Running make on '+self.PACKAGE+'; this may take several minutes')
72      output1,err1,ret  = config.package.Package.executeShellCommand('cd '+self.packageDir+' && ./build.sh && ./make install && ./make clean', timeout=2500, log = self.log)
73    except RuntimeError as e:
74      raise RuntimeError('Error building or installing make '+self.PACKAGE+': '+str(e))
75    self.postInstall(output1+err1, conffile)
76    return self.installDir
77
78  def generateGMakeGuesses(self):
79    if self.argDB['download-make']:
80      self.log.write('Checking downloaded make\n')
81      yield os.path.join(self.installDir,'bin','make')
82      raise RuntimeError('Error! --download-make does not work on this system')
83
84    if 'with-make-exec' in self.argDB:
85      self.log.write('Looking for user provided Make executable '+self.argDB['with-make-exec']+'\n')
86      yield self.argDB['with-make-exec']
87      raise RuntimeError('Error! User provided with-make-exec is not GNU make: '+self.argDB['with-make-exec'])
88
89    if 'with-make-dir' in self.argDB:
90      d = self.argDB['with-make-dir']
91      self.log.write('Looking in user provided directory '+d+'\n')
92      yield os.path.join(d,'bin','make')
93      yield os.path.join(d,'bin','gmake')
94      raise RuntimeError('Error! User provided --with-make-dir=%s but %s/bin does not contain GNU make' % (d, d))
95
96    yield 'make'
97    yield 'gmake'
98
99  def configureMake(self):
100    '''Check Guesses for GNU make'''
101
102    # Check internal make (found in PATH or specified with --download-make, --with-make-exec, --with-make-dir)
103    # Store in self.make
104    for gmake in self.generateGMakeGuesses():
105      self.foundversion, self.haveGNUMake, self.haveGNUMake4, self.haveGNUMake44 = self.checkGNUMake(gmake)
106      if self.haveGNUMake:
107        self.getExecutable(gmake,getFullPath = 1,resultName = 'make')
108        break
109
110    if self.haveGNUMake:
111      # Set user-facing make (self.make_user) to 'make' if found in PATH, otherwise use the internal make (self.make)
112      found = self.getExecutable('make',getFullPath = 0,resultName = 'make_user')
113      if not found:
114        self.getExecutable(self.make,getFullPath = 0,resultName = 'make_user')
115
116      if not self.haveGNUMake4:
117        self.logPrintWarning('You have a version of GNU make older than 4.0. It will work, \
118but may not support all the parallel testing options. You can install the \
119latest GNU make with your package manager, such as Brew or MacPorts, or use \
120the --download-make option to get the latest GNU make')
121      return
122
123    if os.path.exists('/usr/bin/cygcheck.exe'):
124      raise RuntimeError('''\
125Incomplete cygwin install detected: the make utility is missing.
126Please rerun cygwin-setup and select module "make" for install, or try --download-make''')
127    else:
128      raise RuntimeError('''\
129Could not locate the GNU make utility (version greater than or equal to %s) on your system.
130If it is already installed, specify --with-make-exec=<executable> or --with-make-dir=<directory>, or add it to PATH.
131Otherwise try --download-make or install "make" with a package manager.''' % self.minversion)
132
133  def checkGNUMake(self,make):
134    '''Check for GNU make'''
135    foundVersion  = None
136    haveGNUMake   = False
137    haveGNUMake4  = False
138    haveGNUMake44 = False
139    try:
140      import re
141      # accept gnumake version >= self.minversion only [as older version break with gmakefile]
142      (output, error, status) = config.base.Configure.executeShellCommand(make+' --version', log = self.log)
143      gver = re.compile('GNU Make ([0-9]+).([0-9]+)').match(output)
144      if not status and gver:
145        major = int(gver.group(1))
146        minor = int(gver.group(2))
147        if (major,minor) >= self.versionToTuple(self.minversion): haveGNUMake = True
148        if (major > 3): haveGNUMake4 = True
149        foundVersion = ".".join([str(major),str(minor)])
150        if (major,minor) >= (4,4): haveGNUMake44 = True
151    except RuntimeError as e:
152      self.log.write('GNUMake check failed: '+str(e)+'\n')
153    return foundVersion, haveGNUMake, haveGNUMake4, haveGNUMake44
154
155  def setupGNUMake(self):
156    '''Setup other GNU make stuff'''
157    if self.haveGNUMake4 and not self.setCompilers.isDarwin(self.log) and not self.setCompilers.isFreeBSD(self.log):
158      self.paroutflg = "--output-sync=recurse"
159    if self.haveGNUMake44:
160      self.shuffleflg = "--shuffle"
161
162    # Setup make flags
163    self.printdirflag = ' --print-directory'
164    self.noprintdirflag = ' --no-print-directory'
165    # Use rules which look inside archives
166    self.addMakeRule('libc','${LIBNAME}(${OBJSC})')
167    self.addMakeRule('libcxx','${LIBNAME}(${OBJSCXX})')
168    self.addMakeRule('libcu','${LIBNAME}(${OBJSCU})')
169    self.addMakeRule('libf','${OBJSF}','-${AR} ${AR_FLAGS} ${LIBNAME} ${OBJSF}')
170    self.addMakeMacro('OMAKE_PRINTDIR', self.make+self.printdirflag)
171    self.addMakeMacro('OMAKE', self.make+self.noprintdirflag)
172    self.addDefine('OMAKE','"'+self.make+self.noprintdirflag+'"')
173    self.addMakeMacro('MAKE_PAR_OUT_FLG', self.paroutflg)
174    self.addMakeMacro('MAKE_SHUFFLE_FLG', self.shuffleflg)
175    return
176
177  def compute_make_np(self,i):
178    f16 = .80
179    f32 = .65
180    f64 = .50
181    f99 = .30
182    if (i<=2):    return 2
183    elif (i<=4):  return i
184    elif (i<=16): return int(4+(i-4)*f16)
185    elif (i<=32): return int(4+12*f16+(i-16)*f32)
186    elif (i<=64): return int(4+12*f16+16*f32+(i-32)*f64)
187    else:         return int(4+12*f16+16*f32+32*f64+(i-64)*f99)
188    return
189
190  def compute_make_test_np(self,i):
191    f32 = 0.50
192    f99 = 0.35
193    if (i<=2):    return 1
194    elif (i<=4):  return 2
195    elif (i<=32): return int(i*f32)
196    else:         return int(32*f32+(i-32)*f99)
197    return
198
199  def compute_make_load(self,i):
200    f64 = 1.0
201    f99 = .8
202    if (i<=64):   return i*f64
203    else:         return 64*f64+(i-64)*f99
204    return
205
206  def configureMakeNP(self):
207    '''check no of cores on the build machine [perhaps to do make '-j ncores']'''
208    try:
209      import multiprocessing # python-2.6 feature
210      cores = multiprocessing.cpu_count()
211      make_np = self.compute_make_np(cores)
212      make_test_np = self.compute_make_test_np(cores)
213      make_load = self.compute_make_load(cores)
214      self.logPrint('module multiprocessing found %d cores: using make_np = %d' % (cores,make_np))
215    except (ImportError) as e:
216      cores = 2
217      make_np = 2
218      make_test_np = 1
219      make_load = 3
220      self.logPrint('module multiprocessing *not* found: using default make_np = %d' % make_np)
221
222    if 'with-make-np' in self.argDB and self.argDB['with-make-np']:
223        make_np = self.argDB['with-make-np']
224        self.logPrint('using user-provided make_np = %d' % make_np)
225
226    if not self.argDB.get('with-mpi'):
227      make_test_np = make_np
228
229    if 'with-make-test-np' in self.argDB and self.argDB['with-make-test-np']:
230        make_test_np = self.argDB['with-make-test-np']
231        self.logPrint('using user-provided make_test_np = %d' % make_test_np)
232
233    if 'with-make-load' in self.argDB and self.argDB['with-make-load']:
234        make_load = self.argDB['with-make-load']
235        self.logPrint('using user-provided make_load = %f' % make_load)
236
237    self.make_np = make_np
238    self.make_test_np = make_test_np
239    self.make_load = make_load
240    self.addMakeMacro('MAKE_NP',str(make_np))
241    self.addMakeMacro('MAKE_TEST_NP',str(make_test_np))
242    self.addMakeMacro('MAKE_LOAD',str(make_load))
243    self.addMakeMacro('NPMAX',str(cores))
244    self.make_jnp_list = [self.make, '-j'+str(self.make_np), '-l'+str(self.make_load)]
245    self.make_jnp = ' '.join(self.make_jnp_list)
246    return
247
248  def configure(self):
249    config.package.GNUPackage.configure(self)
250    self.executeTest(self.configureMake)
251    self.executeTest(self.setupGNUMake)
252    self.executeTest(self.configureMakeNP)
253    return
254