1# -*- coding: utf-8 -*-
2#
3# Copyright 2013 Oliver Tappe
4# Copyright 2013 Haiku, Inc.
5# Distributed under the terms of the MIT License.
6
7# -- Modules ------------------------------------------------------------------
8
9import os
10import re
11
12from .ConfigParser import ConfigParser
13from .Options import getOption
14from .RecipeTypes import Extendable, MachineArchitecture, YesNo
15from .Utils import sysExit
16
17
18def which(program):
19	def isExecutable(path):
20		return os.path.isfile(path) and os.access(path, os.X_OK)
21
22	path, _ = os.path.split(program)
23	if path:
24		if isExecutable(program):
25			return program
26	else:
27		for path in os.environ["PATH"].split(os.pathsep):
28			path = path.strip('"')
29			candidate = os.path.join(path, program)
30			if isExecutable(candidate):
31				return candidate
32
33	return None
34
35# -----------------------------------------------------------------------------
36
37# allowed types of the configuration file values
38haikuportsAttributes = {
39	'ALLOW_UNTESTED': {
40		'type': YesNo,
41		'required': False,
42		'default': False,
43		'extendable': Extendable.NO,
44		'indexable': False,
45		'setAttribute': 'allowUntested',
46	},
47	'ALLOW_UNSAFE_SOURCES': {
48		'type': YesNo,
49		'required': False,
50		'default': False,
51		'extendable': Extendable.NO,
52		'indexable': False,
53		'setAttribute': 'allowUnsafeSources'
54	},
55	'CREATE_SOURCE_PACKAGES': {
56		'type': YesNo,
57		'required': False,
58		'default': False,
59		'extendable': Extendable.NO,
60		'indexable': False,
61		'setAttribute': 'createSourcePackages',
62	},
63	'CROSS_DEVEL_PACKAGE': {
64		'type': bytes,
65		'required': False,
66		'default': None,
67		'extendable': Extendable.NO,
68		'indexable': False,
69		'optionAttribute': 'crossDevelPackage',
70		'setAttribute': 'crossDevelPackage',
71	},
72	'CROSS_TOOLS': {
73		'type': bytes,
74		'required': False,
75		'default': None,
76		'extendable': Extendable.NO,
77		'indexable': False,
78		'optionAttribute': 'crossTools',
79		'setAttribute': 'crossTools',
80	},
81	'DOWNLOAD_IN_PORT_DIRECTORY': {
82		'type': YesNo,
83		'required': False,
84		'default': False,
85		'extendable': Extendable.NO,
86		'indexable': False,
87		'setAttribute': 'downloadInPortDirectory',
88	},
89	'DOWNLOAD_MIRROR': {
90		'type': bytes,
91		'required': False,
92		'default': 'https://ports-mirror.haiku-os.org',
93		'extendable': Extendable.NO,
94		'indexable': False,
95		'setAttribute': 'downloadMirror',
96	},
97	'LICENSES_DIRECTORY': {
98		'type': bytes,
99		'required': False,
100		'default': None,
101		'extendable': Extendable.NO,
102		'indexable': False,
103		'optionAttribute': 'licensesDirectory',
104		'setAttribute': 'licensesDirectory',
105	},
106	'MIMESET_COMMAND': {
107		'type': bytes,
108		'required': False,
109		'default': None,
110		'extendable': Extendable.NO,
111		'indexable': False,
112		'optionAttribute': 'commandMimeset',
113		'setAttribute': 'mimesetCommand',
114	},
115	'REPORTING_URI': {
116		'type': bytes,
117		'required': False,
118		'default': None,
119		'extendable': Extendable.NO,
120		'indexable': False,
121		'optionAttribute': 'reportingURI',
122		'setAttribute': 'reportingURI',
123	},
124	'OUTPUT_DIRECTORY': {
125		'type': bytes,
126		'required': False,
127		'default': None,
128		'extendable': Extendable.NO,
129		'indexable': False,
130		'setAttribute': 'outputDirectory',
131	},
132	'PACKAGES_PATH': {
133		'type': bytes,
134		'required': False,
135		'default': None,
136		'extendable': Extendable.NO,
137		'indexable': False,
138		'setAttribute': 'packagesPath',
139	},
140	'PACKAGE_COMMAND': {
141		'type': bytes,
142		'required': False,
143		'default': None,
144		'extendable': Extendable.NO,
145		'indexable': False,
146		'optionAttribute': 'commandPackage',
147		'setAttribute': 'packageCommand',
148	},
149	'PACKAGE_REPO_COMMAND': {
150		'type': bytes,
151		'required': False,
152		'default': None,
153		'extendable': Extendable.NO,
154		'indexable': False,
155		'optionAttribute': 'commandPackageRepo',
156		'setAttribute': 'packageRepoCommand',
157	},
158	'PACKAGER': {
159		'type': bytes,
160		'required': True,
161		'default': None,
162		'extendable': Extendable.NO,
163		'indexable': False,
164		'setAttribute': 'packager',
165	},
166	'REPOSITORY_PATH': {
167		'type': bytes,
168		'required': False,
169		'default': None,
170		'extendable': Extendable.NO,
171		'indexable': False,
172		'setAttribute': 'repositoryPath',
173	},
174	'SECONDARY_CROSS_DEVEL_PACKAGES': {
175		'type': list,
176		'required': False,
177		'default': [],
178		'extendable': Extendable.NO,
179		'indexable': False,
180		'optionAttribute': 'secondaryCrossDevelPackages',
181		'setAttribute': 'secondaryCrossDevelPackages',
182	},
183	'SECONDARY_CROSS_TOOLS': {
184		'type': list,
185		'required': False,
186		'default': [],
187		'extendable': Extendable.NO,
188		'indexable': False,
189	},
190	'SECONDARY_TARGET_ARCHITECTURES': {
191		'type': list,
192		'required': False,
193		'default': [],
194		'extendable': Extendable.NO,
195		'indexable': False,
196		'setAttribute': 'secondaryArchitectures',
197	},
198	'SOURCEFORGE_MIRROR': {
199		'type': bytes,
200		'required': False,
201		'default': None,
202		'extendable': Extendable.NO,
203		'indexable': False,
204		'optionAttribute': 'sourceforgeMirror',
205		'setAttribute': 'sourceforgeMirror',
206	},
207	'TARGET_ARCHITECTURE': {
208		'type': MachineArchitecture,
209		'required': False,
210		'default': None,
211		'extendable': Extendable.NO,
212		'indexable': False,
213		'setAttribute': 'targetArchitecture',
214	},
215	'TREE_PATH': {
216		'type': bytes,
217		'required': True,
218		'default': None,
219		'extendable': Extendable.NO,
220		'indexable': False,
221		'setAttribute': 'treePath',
222	},
223	'SYSTEM_MIME_DB': {
224		'type': bytes,
225		'required': False,
226		'default': None,
227		'extendable': Extendable.NO,
228		'indexable': False,
229		'optionAttribute': 'systemMimeDB',
230		'setAttribute': 'systemMimeDB',
231	},
232	'VENDOR': {
233		'type': bytes,
234		'required': False,
235		'default': 'Haiku Project',
236		'extendable': Extendable.NO,
237		'indexable': False,
238		'setAttribute': 'vendor',
239	},
240}
241
242
243# -- Configuration class ------------------------------------------------------
244
245class Configuration(object):
246	configuration = None
247
248	def __init__(self):
249		self.treePath = None
250		self.isCrossBuildRepository = False
251		self.targetArchitecture = None
252		self.secondaryArchitectures = None
253		self.packager = None
254		self.packagerName = None
255		self.packagerEmail = None
256		self.allowUntested = False
257		self.allowUnsafeSources = False
258		self.createSourcePackages = True
259		self.downloadInPortDirectory = False
260		self.packageCommand = None
261		self.packageRepoCommand = None
262		self.mimesetCommand = None
263		self.reportingURI = None
264		self.systemMimeDB = None
265		self.licensesDirectory = None
266		self.crossTools = None
267		self.secondaryCrossTools = {}
268		self.crossDevelPackage = None
269		self.secondaryCrossDevelPackages = None
270		self.outputDirectory = None
271		self.repositoryPath = None
272		self.vendor = None
273		self.packagesPath = None
274		self.sourceforgeMirror = None
275
276		self._readConfigurationFile()
277
278		if not self.outputDirectory:
279			self.outputDirectory = self.treePath
280		if not self.packagesPath:
281			self.packagesPath = os.path.join(self.outputDirectory, 'packages')
282		if not self.repositoryPath:
283			self.repositoryPath = os.path.join(self.outputDirectory, 'repository')
284
285	@staticmethod
286	def init():
287		Configuration.configuration = Configuration()
288
289	@staticmethod
290	def getTreePath():
291		return Configuration.configuration.treePath
292
293	@staticmethod
294	def isCrossBuildRepository():
295		return Configuration.configuration.isCrossBuildRepository
296
297	@staticmethod
298	def getTargetArchitecture():
299		return Configuration.configuration.targetArchitecture
300
301	@staticmethod
302	def getSecondaryTargetArchitectures():
303		return Configuration.configuration.secondaryArchitectures
304
305	@staticmethod
306	def getPackager():
307		return Configuration.configuration.packager
308
309	@staticmethod
310	def getPackagerName():
311		return Configuration.configuration.packagerName
312
313	@staticmethod
314	def getPackagerEmail():
315		return Configuration.configuration.packagerEmail
316
317	@staticmethod
318	def shallAllowUntested():
319		return Configuration.configuration.allowUntested
320
321	@staticmethod
322	def shallAllowUnsafeSources():
323		return Configuration.configuration.allowUnsafeSources
324
325	@staticmethod
326	def shallCreateSourcePackages():
327		return Configuration.configuration.createSourcePackages
328
329	@staticmethod
330	def shallDownloadInPortDirectory():
331		return Configuration.configuration.downloadInPortDirectory
332
333	@staticmethod
334	def getPackageCommand():
335		if Configuration.configuration.packageCommand is None:
336			return which("package")
337		return Configuration.configuration.packageCommand
338
339	@staticmethod
340	def getPackageRepoCommand():
341		if Configuration.configuration.packageRepoCommand is None:
342			return which("package_repo")
343		return Configuration.configuration.packageRepoCommand
344
345	@staticmethod
346	def getMinisignCommand():
347		return which("minisign")
348
349	@staticmethod
350	def getMimesetCommand():
351		if Configuration.configuration.mimesetCommand is None:
352			return which("mimeset")
353		return Configuration.configuration.mimesetCommand
354
355	@staticmethod
356	def getReportingURI():
357		return Configuration.configuration.reportingURI
358
359	@staticmethod
360	def getSystemMimeDbDirectory():
361		return Configuration.configuration.systemMimeDB
362
363	@staticmethod
364	def getLicensesDirectory():
365		return Configuration.configuration.licensesDirectory
366
367	@staticmethod
368	def getCrossToolsDirectory():
369		return Configuration.configuration.crossTools
370
371	@staticmethod
372	def getSecondaryCrossToolsDirectory(architecture):
373		return Configuration.configuration.secondaryCrossTools.get(architecture)
374
375	@staticmethod
376	def getCrossDevelPackage():
377		return Configuration.configuration.crossDevelPackage
378
379	@staticmethod
380	def getSecondaryCrossDevelPackage(architecture):
381		return Configuration.configuration.secondaryCrossDevelPackages.get(
382			architecture)
383
384	@staticmethod
385	def getOutputDirectory():
386		return Configuration.configuration.outputDirectory
387
388	@staticmethod
389	def getRepositoryPath():
390		return Configuration.configuration.repositoryPath
391
392	@staticmethod
393	def getPackagesPath():
394		return Configuration.configuration.packagesPath
395
396	@staticmethod
397	def getDownloadMirror():
398		return Configuration.configuration.downloadMirror
399
400	@staticmethod
401	def getSourceforgeMirror():
402		return Configuration.configuration.sourceforgeMirror
403
404	@staticmethod
405	def getVendor():
406		return Configuration.configuration.vendor
407
408	def _readConfigurationFile(self):
409		# Find the configuration file. It may be
410		# * specified on the command line,
411		# * in the current directory,
412		# * '~/config/settings/haikuports.conf', or
413		# * '/system/settings/haikuports.conf'.
414		haikuportsConf = getOption('configFile')
415		if not haikuportsConf:
416			haikuportsConf = 'haikuports.conf'
417			if not os.path.exists(haikuportsConf):
418				haikuportsConf = (os.path.expanduser('~')
419					+ '/config/settings/haikuports.conf')
420				if not os.path.exists(haikuportsConf):
421					haikuportsConf = '/system/settings/haikuports.conf'
422
423		if not os.path.exists(haikuportsConf):
424			sysExit("Unable to find haikuports.conf in known search paths.\n"
425				+ u"See haikuports-sample.conf for more information")
426
427		configParser = ConfigParser(haikuportsConf, haikuportsAttributes, {})
428		configurationValue = configParser.getEntriesForExtension('')
429
430		# check whether all required values are present
431		for key in haikuportsAttributes.keys():
432			if 'optionAttribute' in haikuportsAttributes[key]:
433				optionAttribute = haikuportsAttributes[key]['optionAttribute']
434				optionValue = getOption(optionAttribute)
435				if optionValue:
436					configurationValue[key] = optionValue
437
438			if key not in configurationValue:
439				if haikuportsAttributes[key]['required']:
440					sysExit("Required value '" + key + u"' not present in "
441							+ haikuportsConf)
442
443				# set default value, as no other value has been provided
444				if haikuportsAttributes[key]['default'] is not None:
445					configurationValue[key] \
446						= haikuportsAttributes[key]['default']
447
448			if ('setAttribute' in haikuportsAttributes[key]
449				and key in configurationValue):
450				setAttribute = haikuportsAttributes[key]['setAttribute']
451				setattr(self, setAttribute, configurationValue[key])
452
453		self.treePath = self.treePath.rstrip('/')
454
455		# determine if we are using a cross-build repository
456		self.isCrossBuildRepository = os.path.exists(self.treePath + '/.cross')
457		if self.isCrossBuildRepository and not self.targetArchitecture:
458			sysExit('For a cross-build repository, TARGET_ARCHITECTURE '
459				'needs to be set in ' + haikuportsConf)
460
461		# split packager into name and email:
462		m = re.match(r'^\s*(?P<name>.+?)\s*<(?P<email>.+?)>$', self.packager)
463		if not m:
464			sysExit("Couldn't parse name/email from PACKAGER value "
465					+ self.packager)
466		self.packagerName = m.group('name')
467		self.packagerEmail = m.group('email')
468
469		# get the secondary cross tools and devel packages
470		if self.secondaryArchitectures:
471			crossTools = configurationValue.get('SECONDARY_CROSS_TOOLS')
472			if crossTools:
473				if len(crossTools) != len(self.secondaryArchitectures):
474					sysExit('A cross-tools directory must be specified for '
475						'each secondary architecture')
476				for architecture, tools \
477						in zip(self.secondaryArchitectures, crossTools):
478					self.secondaryCrossTools[architecture] = tools
479
480			if self.secondaryCrossDevelPackages:
481				crossDevelPackages = self.secondaryCrossDevelPackages
482				self.secondaryCrossDevelPackages = {}
483				if len(crossDevelPackages) != len(self.secondaryArchitectures):
484					sysExit('A cross-tools devel pacakge must be specified for '
485						'each secondary architecture')
486				for architecture, package \
487						in zip(self.secondaryArchitectures, crossDevelPackages):
488					self.secondaryCrossDevelPackages[architecture] = package
489			else:
490				self.secondaryCrossDevelPackages = {}
491		else:
492			self.secondaryCrossTools = {}
493			self.secondaryCrossDevelPackages = {}
494