1# -*- coding: utf-8 -*-
2#
3# Copyright 2007-2011 Brecht Machiels
4# Copyright 2009-2010 Chris Roberts
5# Copyright 2009-2011 Scott McCreary
6# Copyright 2009 Alexander Deynichenko
7# Copyright 2009 HaikuBot (aka RISC)
8# Copyright 2010-2011 Jack Laxson (Jrabbit)
9# Copyright 2011 Ingo Weinhold
10# Copyright 2013 Oliver Tappe
11# Distributed under the terms of the MIT License.
12
13# -- Modules ------------------------------------------------------------------
14
15import os
16import re
17import sys
18import traceback
19from subprocess import check_call
20
21from .BuildPlatform import buildPlatform
22from .Configuration import Configuration
23from .DependencyAnalyzer import DependencyAnalyzer
24from .Options import getOption
25from .PackageRepository import PackageRepository
26from .Policy import Policy
27from .RecipeAttributes import getRecipeFormatVersion
28from .RecipeTypes import MachineArchitecture
29from .Repository import Repository
30from .Utils import (ensureCommandIsAvailable, haikuportsRepoUrl, info, sysExit,
31                    warn)
32
33# -- Main Class ---------------------------------------------------------------
34
35class Main(object):
36	def __init__(self, options, args):
37		self.options = options
38		try:
39			self.run(args)
40		except BaseException as exception:
41			if getOption('debug'):
42				traceback.print_exc()
43			elif type(exception).__name__ == "SystemExit":
44				if type(exception.code).__name__ != "int":
45					print(exception.code)
46			else:
47				print(exception)
48			exit(1)
49
50	def run(self, args):
51
52		self.policy = Policy(self.options.strictPolicy)
53
54		self.repository = None
55
56		# read global settings
57		Configuration.init()
58
59		self.treePath = Configuration.getTreePath()
60		self.outputDirectory = Configuration.getOutputDirectory()
61		self.packagesPath = Configuration.getPackagesPath()
62		self.repositoryPath = Configuration.getRepositoryPath()
63
64		self.packageRepositories = [self.packagesPath]
65		if not self.options.noSystemPackages \
66			and self.options.systemPackagesDirectory is not None:
67			self.packageRepositories.append(
68				self.options.systemPackagesDirectory)
69
70		# if requested, checkout or update ports tree
71		if self.options.get:
72			self._updatePortsTree()
73			return
74
75		# create path where built packages will be collected
76		if not os.path.exists(self.packagesPath):
77			os.mkdir(self.packagesPath)
78
79		self._checkFormatVersions()
80
81		# determine if haikuporter has just been invoked for a short-term
82		# command
83		self.shallowInitIsEnough = (self.options.lint or self.options.tree
84			or self.options.get or self.options.list
85			or self.options.portsForFiles
86			or self.options.portsForPackages
87			or self.options.listPackages
88			or self.options.listDependencies
89			or self.options.search
90			or self.options.searchPackages
91			or self.options.about
92			or self.options.location
93			or self.options.buildMaster
94			or self.options.repositoryUpdate
95			or self.options.prunePackageRepository
96			or self.options.createPackageRepository
97			or self.options.why
98			or self.options.analyzeDependencies
99			or self.options.checkPackageRepositoryConsistency
100			or self.options.checkRepositoryConsistency
101			or self.options.checkPortsReleases)
102
103		# init build platform
104		buildPlatform.init(self.treePath, self.outputDirectory,
105			self.packagesPath, self.shallowInitIsEnough)
106
107		# set up the global variables we'll inherit to the shell
108		self._initGlobalShellVariables()
109
110		if self.options.buildMaster:
111			from .BuildMaster import BuildMaster
112			self.buildMaster = BuildMaster(self.treePath, self.packagesPath,
113				self.options)
114
115			self.options.allDependencies = True
116			self.options.noPackageObsoletion = True
117			self.options.ignoreMessages = True
118
119		if self.options.repositoryUpdate \
120			or self.options.checkRepositoryConsistency:
121			self._createRepositoryIfNeeded(self.options.quiet)
122
123			if self.options.checkRepositoryConsistency:
124				self.repository.checkRepositoryConsistency(self.options.verbose)
125			return
126
127		if self.options.prunePackageRepository \
128			or self.options.createPackageRepository \
129			or self.options.checkPackageRepositoryConsistency:
130
131			self.options.noPackageObsoletion = True
132			self._createRepositoryIfNeeded(True)
133
134			packageRepository = PackageRepository(self.packagesPath,
135				self.repository, self.options.quiet, self.options.verbose)
136
137			if self.options.prunePackageRepository:
138				packageRepository.prune()
139
140			if self.options.checkPackageRepositoryConsistency:
141				packageRepository.checkPackageRepositoryConsistency()
142
143			if self.options.createPackageRepository:
144				packageRepository.createPackageRepository(
145					self.options.createPackageRepository)
146			return
147
148		# if requested, print the location of the haikuports source tree
149		if self.options.tree:
150			print(self.treePath)
151			return
152
153		# if requested, scan the ports tree for problems
154		if self.options.lint:
155			if (not buildPlatform.isHaiku
156				and Configuration.getLicensesDirectory() is None):
157				sysExit('LICENSES_DIRECTORY must be set in configuration on '
158					'this build platform!')
159			self._createRepositoryIfNeeded(True)
160			if not args:
161				self._checkSourceTree("")
162			else:
163				self._checkSourceTree(args[0])
164			return
165
166		# if requested, list all ports in the HaikuPorts tree
167		if self.options.list or self.options.listPackages:
168			self._createRepositoryIfNeeded(True)
169			if self.options.list:
170				allNames = self.repository.searchPorts(None,
171					self.options.printFilenames)
172			else:
173				allNames = self.repository.searchPackages(None,
174					self.options.printFilenames)
175
176			for name in sorted(allNames):
177				print(name)
178			return
179
180		# if requested, search for a port
181		if self.options.search or self.options.searchPackages:
182			if not args:
183				sysExit('You need to specify a search string.\n'
184						u"Invoke '" + sys.argv[0] + u" -h' for usage "
185						u"information.")
186			self._createRepositoryIfNeeded(True)
187
188			for arg in args:
189				if self.options.search:
190					portNames = self.repository.searchPorts(arg)
191					for portName in portNames:
192						versions = self.repository.portVersionsByName[portName]
193						portID = portName + '-' + versions[0]
194						port = self.repository.allPorts[portID]
195						if self.options.printRaw:
196							print(portName)
197						else:
198							print(port.category + '::' + portName)
199				else:
200					packageNames = self.repository.searchPackages(arg,
201						self.options.printFilenames)
202					for packageName in packageNames:
203						print(packageName)
204			return
205
206		# if requested, print the ports related to the supplied files
207		if self.options.portsForFiles:
208			self._createRepositoryIfNeeded(True)
209
210			if self.options.activeVersionsOnly:
211				allPorts = self.repository.activePorts
212			else:
213				allPorts = self.repository.allPorts.values()
214
215			files = [arg if os.path.isabs(arg) \
216				else os.path.join(self.treePath, arg) for arg in args]
217
218			for port in allPorts:
219				if port.referencesFiles(files):
220					print(port.versionedName)
221
222			return
223
224		# if requested, print the ports producing the supplied packages
225		if self.options.portsForPackages:
226			self._createRepositoryIfNeeded(True)
227
228			ports = set()
229			for port in self.repository.allPorts.values():
230				try:
231					port.parseRecipeFileIfNeeded()
232				except:
233					continue
234
235				for package in port.packages:
236					if package.hpkgName in args:
237						ports.add(port.versionedName)
238
239			print('\n'.join(sorted(ports)))
240			return
241
242		if self.options.location:
243			if not args:
244				sysExit('You need to specify a search string.\n'
245						u"Invoke '" + sys.argv[0] + u" -h' for usage "
246						u"information.")
247			# Provide the installed location of a port (for quick editing)
248			self._createRepositoryIfNeeded(True)
249			portNames = self.repository.searchPorts(args[0])
250			for portName in portNames:
251				versions = self.repository.portVersionsByName[portName]
252				portID = portName + '-' + versions[0]
253				port = self.repository.allPorts[portID]
254				print(os.path.join(self.treePath, port.category, portName))
255			return
256
257		if self.options.portsfile:
258			# read portslist from file and convert into list of requires
259			with open(self.options.portsfile, 'r') as portsFile:
260				ports = [p.strip() for p in portsFile.readlines()]
261			ports = [p for p in ports if len(p) > 0]
262			portsfileAsRequires = []
263			for port in ports:
264				portSpec = self._splitPortSpecIntoNameVersionAndRevision(port)
265				if portSpec['version']:
266					portsfileAsRequires.append(portSpec['name'] + ' =='
267											   + portSpec['version'])
268				else:
269					portsfileAsRequires.append(portSpec['name'])
270			if not portsfileAsRequires:
271				sysExit("The given ports-file doesn't contain any ports.")
272			self.shellVariables['portsfileAsRequires'] \
273				= '\n'.join(portsfileAsRequires)
274
275		self._createRepositoryIfNeeded(self.options.quiet, self.options.verbose)
276
277		if self.options.analyzeDependencies:
278			DependencyAnalyzer(self.repository).printDependencies()
279			return
280
281		# if requested, check for newer upstream releases
282		if self.options.checkPortsReleases:
283			self._createRepositoryIfNeeded(True)
284			if not args:
285				self._checkPortsReleases("")
286			else:
287				self._checkPortsReleases(args[0])
288			return
289
290		bootstrapPorts = set()
291
292		# if a ports-file has been given, read port specifications from it
293		# and build them all (as faked requires of a specific meta port, such
294		# that their runtime requires get pulled in, too)
295		self.portSpecs = []
296		self.builtPortIDs = set()
297		if self.options.portsfile:
298			# pretend the meta port responsible for building a list of ports
299			# has been specified on the cmdline
300			metaPortSpec = 'meta_portsfile-1'
301			if metaPortSpec not in self.repository.allPorts:
302				sysExit("no recipe found for '%s'" % metaPortSpec)
303			self.portSpecs.append(
304				self._splitPortSpecIntoNameVersionAndRevision(metaPortSpec))
305		elif self.options.doBootstrap:
306			# first untangle and build all ports with circular dependencies
307			dependencyAnalyzer = DependencyAnalyzer(self.repository)
308			portsToBuild = dependencyAnalyzer.getBuildOrderForBootstrap()
309			print('Untangling the ports with circular dependencies gave this:')
310			print("	 " + "\n  ".join(portsToBuild))
311			print('After that, all other available ports will be built, too')
312			portsNotYetBuilt = []
313			for portId in portsToBuild:
314				port = self.repository.allPorts[portId]
315				mainPackage = port.mainPackage
316				if (mainPackage
317					and os.path.exists(
318						self.packagesPath + '/' + mainPackage.hpkgName)):
319					print('skipping port %s, since its main package already '
320						'exists' % portId)
321					continue
322				portsNotYetBuilt.append(portId)
323				bootstrapPorts.add(portId)
324			# add all other ports, such that all available ports will be built
325			for portId in self.repository.allPorts.keys():
326				if portId not in bootstrapPorts:
327					port = self.repository.allPorts[portId]
328					mainPackage = port.mainPackage
329					if (mainPackage
330						and os.path.exists(
331							self.packagesPath + '/' + mainPackage.hpkgName)):
332						print('skipping port %s, since its main package '
333							'already exists' % portId)
334						continue
335					portsNotYetBuilt.append(portId)
336			# add all ports as if they were given on the cmdline
337			self.portSpecs = [
338				self._splitPortSpecIntoNameVersionAndRevision(port)
339				for port in portsNotYetBuilt
340			]
341		else:
342			# if there is no argument given, exit
343			if not args:
344				sysExit('You need to specify a search string.\nInvoke '
345						u"'" + sys.argv[0] + u" -h' for usage information.")
346			self.portSpecs = [
347				self._splitPortSpecIntoNameVersionAndRevision(port)
348					for port in args
349			]
350
351		# don't build or package when not patching
352		if not self.options.patch:
353			self.options.build = False
354			self.options.package = False
355
356		# collect all available ports and validate each specified port
357		allPorts = self.repository.allPorts
358		portVersionsByName = self.repository.portVersionsByName
359		for portSpec in self.portSpecs:
360
361			# validate name of port
362			portName = portSpec['name']
363			if portName not in portVersionsByName:
364				# for cross-build repository, try with target arch added
365				portNameFound = False
366				if Configuration.isCrossBuildRepository():
367					nameWithTargetArch \
368						= (portName + '_'
369						+ self.shellVariables['targetArchitecture'])
370					if nameWithTargetArch in portVersionsByName:
371						portName = nameWithTargetArch
372						portNameFound = True
373
374				# it might actually be a package name
375				if not portNameFound:
376					portName = self.repository.getPortNameForPackageName(
377						portName)
378					if not portName:
379						if self.options.buildMaster:
380							self.buildMaster.addSkipped(portSpec['name'],
381								'not found in repository')
382							continue
383
384						sysExit(portSpec['name'] + ' not found in repository')
385
386				portSpec['name'] = portName
387
388			# use specific version if given, otherwise use the highest buildable
389			# version
390			if portSpec['version']:
391				portID = portSpec['name'] + '-' + portSpec['version']
392			else:
393				version = self.repository.getActiveVersionOf(portSpec['name'],
394															 True)
395				if not version:
396					if self.options.buildMaster:
397						self.buildMaster.addSkipped(portSpec['name'],
398							'no version of ' + portSpec['name']
399								+ ' can be built')
400						continue
401					else:
402						sysExit('No version of ' + portSpec['name']
403							+ ' can be built')
404				portID = portSpec['name'] + '-' + version
405
406			if portID not in allPorts:
407				if self.options.buildMaster:
408					self.buildMaster.addSkipped(portID, 'not found in tree')
409					continue
410
411				sysExit(portID + ' not found in tree.')
412
413			port = allPorts[portID]
414
415			# show port description, if requested
416			if self.options.about:
417				try:
418					port.parseRecipeFile(False)
419				except:
420					pass
421				port.printDescription()
422				continue
423
424			if self.options.listDependencies:
425				self._listDependencies(port)
426				continue
427
428			if not self._validateMainPort(port, portSpec['revision']):
429				continue
430
431			portSpec['id'] = portID
432
433		if self.options.about or self.options.listDependencies:
434			return
435
436		if self.options.why:
437			# find out about why another port is required
438			port = allPorts[self.portSpecs[0]['id']]
439			whySpec = self._splitPortSpecIntoNameVersionAndRevision(
440				self.options.why)
441			if not whySpec['version']:
442				whySpec['version'] \
443					= self.repository.getActiveVersionOf(whySpec['name'],
444														 False)
445			whyID = whySpec['name'] + '-' + whySpec['version']
446			if whyID not in allPorts:
447				sysExit(whyID + ' not found in tree.')
448			requiredPort = allPorts[whyID]
449			self._validateMainPort(requiredPort)
450			port.whyIsPortRequired(self.packagesPath, requiredPort)
451			return
452
453		# do whatever's needed to the list of ports
454		for portSpec in self.portSpecs:
455			if 'id' not in portSpec:
456				continue
457
458			port = allPorts[portSpec['id']]
459
460			if self.options.clean:
461				port.cleanWorkDirectory()
462			elif self.options.purge:
463				port.purge()
464			elif ((self.options.build and portSpec['id'] not in bootstrapPorts)
465					or self.options.test) and self.options.allDependencies:
466				try:
467					self._buildMainPort(port, self.options.test)
468				except SystemExit as exception:
469					if not self.options.buildMaster:
470						raise
471					else:
472						self.buildMaster.addSkipped(port, str(exception))
473
474			elif self.options.extractPatchset:
475				port.extractPatchset()
476			else:
477				self._buildPort(port, True, self.options.test)
478
479		# show summary of policy violations
480		if Policy.violationsByPort:
481			print('Summary of policy violations in this session:')
482			for portName in sorted(Policy.violationsByPort.keys()):
483				print('Policy violations of %s:' + portName)
484				for violation in Policy.violationsByPort[portName]:
485					print('\t' + violation)
486
487		if self.options.buildMaster:
488			if self.options.display:
489				from .Display import DisplayContext
490				with DisplayContext() as ctxt:
491					self.buildMaster.runBuilds(ctxt.stdscr)
492			else:
493				self.buildMaster.runBuilds()
494
495	def _listDependencies(self, port):
496		print('-' * 70)
497		print('dependencies of ' + port.versionedName)
498
499		presentDependencyPackages = []
500		buildDependencies = port.resolveDependencies(
501			self.packageRepositories, self.options.test,
502			presentDependencyPackages)
503
504		print('packages already present:')
505		presentDependencyPackageNames = [os.path.basename(package)
506			for package in presentDependencyPackages]
507		for name in sorted(presentDependencyPackageNames):
508			print("\t" + name)
509		print('')
510
511		print('packages that need to be built:')
512		for dependency in buildDependencies:
513			packageInfoFileName = os.path.basename(dependency)
514			packageID = packageInfoFileName[:packageInfoFileName.rindex('.')]
515			try:
516				portID = self.repository.getPortIdForPackageId(packageID)
517				print("\t" + packageID + ' -> ' + portID)
518
519			except KeyError:
520				sysExit('Inconsistency: ' + port.versionedName
521					+ ' requires ' + packageID
522					+ ' but no corresponding port was found!')
523
524	def _validateMainPort(self, port, revision=None):
525		"""Parse the recipe file for the given port and get any required
526		   confirmations"""
527
528		# read data from the recipe file
529		port.parseRecipeFile(True)
530
531		# if a specific revision has been given, check if this port matches it
532		if revision and port.revision != revision:
533			sysExit((u"Port %s isn't available in revision %s (found revision "
534					+ '%s instead)')
535					% (port.versionedName, revision, port.revision))
536
537		# warn when the port is not buildable on this architecture
538		if not port.isBuildableOnTargetArchitecture():
539			status = port.statusOnTargetArchitecture
540			message = 'Port {} is {} on this architecture.'.format(
541				port.versionedName, status)
542			warn(message)
543			if self.options.buildMaster:
544				self.buildMaster.addSkipped(port, message)
545				return False
546
547			if not self.options.yes:
548				answer = input('Continue (y/n + enter)? ')
549				if answer == '':
550					sys.exit(1)
551				if answer[0].lower() == 'y':
552					print(' ok')
553				else:
554					sys.exit(1)
555
556		if not self.options.ignoreMessages and port.recipeKeys['MESSAGE']:
557			print(port.recipeKeys['MESSAGE'])
558			if not self.options.yes:
559				answer = raw_input('Continue (y/n + enter)? ')
560				if answer == '':
561					sys.exit(1)
562				if answer[0].lower() == 'y':
563					print(' ok')
564				else:
565					sys.exit(1)
566
567		return True
568
569	def _buildMainPort(self, port, testPort):
570		"""Build the given port with all its dependencies"""
571
572		if port.versionedName in self.builtPortIDs:
573			return
574
575		self._setupForPossiblyObsoletePort(port)
576
577		print('=' * 70)
578		print(port.category + '::' + port.versionedName)
579		print('=' * 70)
580
581		allPorts = self.repository.allPorts
582
583		buildDependencies = None
584		presentDependencyPackages = None
585		if self.options.buildMaster:
586			presentDependencyPackages = []
587			try:
588				buildDependencies = port.resolveDependencies(
589					self.packageRepositories, False, presentDependencyPackages)
590			except Exception as exception:
591				self.buildMaster.addSkipped(port,
592					'resolving build dependencies failed: {}'.format(exception))
593				return
594		else:
595			buildDependencies = port.resolveDependencies(
596				self.packageRepositories, testPort)
597
598		print('The following build dependencies were found:')
599		for dependency in buildDependencies:
600			print('\t' + dependency)
601
602		requiredPortsToBuild = []
603		requiredPortIDs = set()
604		requiredPackageIDs = set()
605		for dependency in buildDependencies:
606			packageInfoFileName = os.path.basename(dependency)
607			packageID = packageInfoFileName[:packageInfoFileName.rindex('.')]
608			if self.options.buildMaster and packageID not in requiredPackageIDs:
609				requiredPackageIDs.add(packageID)
610
611			try:
612				portID = self.repository.getPortIdForPackageId(packageID)
613				if portID not in requiredPortIDs:
614					requiredPort = allPorts[portID]
615					if ((getOption('createSourcePackagesForBootstrap')
616							or getOption('createSourcePackages'))
617						and (not requiredPort.sourcePackage
618							or requiredPort.sourcePackageExists(
619								self.packagesPath))):
620						continue
621					requiredPortsToBuild.append(requiredPort)
622					requiredPortIDs.add(portID)
623			except KeyError:
624				sysExit('Inconsistency: ' + port.versionedName
625						 + ' requires ' + packageID
626						 + ' but no corresponding port was found!')
627
628		if requiredPortsToBuild:
629			if port in requiredPortsToBuild:
630				sysExit('Port ' + port.versionedName + ' depends on itself')
631
632			print('The following required ports will be built first:')
633			for requiredPort in requiredPortsToBuild:
634				print('\t' + requiredPort.category + '::'
635					  + requiredPort.versionedName)
636			for requiredPort in requiredPortsToBuild:
637				if self.options.buildMaster:
638					requiredPort.parseRecipeFile(True)
639					try:
640						self._buildMainPort(requiredPort, False)
641					except SystemExit as exception:
642						self.buildMaster.addSkipped(port,
643							'Skipping ' + port.versionedName + ', dependency '
644								+ requiredPort.versionedName
645								+ ' cannot be built: ' + str(exception))
646						sysExit('Dependency of ' + port.versionedName
647							+ ' cannot be built')
648				else:
649					self._buildPort(requiredPort, True, False)
650
651		if self.options.buildMaster:
652			self.buildMaster.schedule(port, requiredPackageIDs,
653				presentDependencyPackages)
654			self.builtPortIDs.add(port.versionedName)
655		else:
656			self._buildPort(port, False, testPort)
657
658	def _buildPort(self, port, parseRecipe, testPort):
659		"""Build a single port"""
660
661		if port.versionedName in self.builtPortIDs:
662			return
663
664		targetPath = self._setupForPossiblyObsoletePort(port)
665
666		print('-' * 70)
667		print(port.category + '::' + port.versionedName)
668		print('\t' + port.recipeFilePath)
669		print('-' * 70)
670
671		# pass-on options to port
672		port.forceOverride = self.options.force
673		port.beQuiet = self.options.quiet
674		port.avoidChroot = not self.options.chroot
675
676		if parseRecipe:
677			port.parseRecipeFile(True)
678
679		if testPort and port.checkFlag('build'):
680			self._testPort(port)
681			return
682
683		if not port.isMetaPort:
684			port.downloadSource()
685			port.unpackSource()
686			port.populateAdditionalFiles()
687			if self.options.patch:
688				port.patchSource()
689
690		if self.options.build:
691			port.build(self.packagesPath, self.options.package, targetPath)
692
693		if testPort:
694			self._testPort(port)
695
696		self.builtPortIDs.add(port.versionedName)
697
698	def _setupForPossiblyObsoletePort(self, port):
699		# HPKGs are usually written into the 'packages' directory, but when
700		# an obsolete port (one that's not in the repository) is being built,
701		# its packages are stored into the .obsolete subfolder of the packages
702		# directory.
703		targetPath = self.packagesPath
704		activeVersion = self.repository.getActiveVersionOf(port.name)
705		if port.version != activeVersion:
706			targetPath += '/.obsolete'
707			if not os.path.exists(targetPath):
708				os.makedirs(targetPath)
709
710			warn('building obsolete port, packages will be put in {}'.format(
711					targetPath))
712
713			# make sure the correct dependencyInfo-file has been created
714			self.repository.supportBackwardsCompatibility(port.name,
715				port.version)
716
717		return targetPath
718
719	def _testPort(self, port):
720		"""Build a single port"""
721
722		print('-' * 70)
723		print('TESTING ' + port.category + '::' + port.versionedName)
724		print('-' * 70)
725
726		# pass-on options to port
727		port.beQuiet = self.options.quiet
728
729		port.test(self.packagesPath)
730
731	def _initGlobalShellVariables(self):
732		# get the target haiku version and architecture
733		targetArchitecture = buildPlatform.targetArchitecture
734		if Configuration.isCrossBuildRepository():
735			targetHaikuPackage = Configuration.getCrossDevelPackage()
736			if not targetHaikuPackage:
737				if not buildPlatform.isHaiku:
738					sysExit('On this platform a haiku cross devel package '
739						'must be specified (via --cross-devel-package)')
740				targetHaikuPackage = ('/boot/system/develop/cross/'
741					+ 'haiku_cross_devel_sysroot_%s.hpkg') \
742					% targetArchitecture
743		else:
744			if (not buildPlatform.isHaiku and not self.shallowInitIsEnough
745				and not (getOption('createSourcePackagesForBootstrap')
746					or getOption('createSourcePackages'))):
747				sysExit('Native building not supported on this platform '
748					'(%s)' % buildPlatform.name)
749
750		self.shellVariables = {
751			'haikuVersion': 'r1~alpha1',	# just a dummy value for compatibility with old recipes
752			'buildArchitecture': buildPlatform.architecture,
753			'targetArchitecture': targetArchitecture,
754			'jobs': str(self.options.jobs),
755		}
756		if self.options.jobs > 1:
757			self.shellVariables['jobArgs'] = '-j' + str(self.options.jobs)
758		if self.options.quiet:
759			self.shellVariables['quiet'] = '1'
760
761		if Configuration.isCrossBuildRepository():
762			self.shellVariables['isCrossRepository'] = 'true'
763
764			buildMachineTriple = buildPlatform.machineTriple
765			targetMachineTriple \
766				= MachineArchitecture.getTripleFor(targetArchitecture)
767
768			# If build- and target machine triple are the same, force a
769			# cross-build by faking the build-machine triple as something
770			# different (which is still being treated identically by the actual
771			# build process).
772			if buildMachineTriple == targetMachineTriple:
773				buildMachineTriple += '_build'
774
775			self.shellVariables['buildMachineTriple'] = buildMachineTriple
776			self.shellVariables['buildMachineTripleAsName'] \
777				= buildMachineTriple.replace('-', '_')
778			self.shellVariables['targetArchitecture'] = targetArchitecture
779			self.shellVariables['targetMachineTriple'] = targetMachineTriple
780			self.shellVariables['targetMachineTripleAsName'] \
781				= targetMachineTriple.replace('-', '_')
782		else:
783			self.shellVariables['isCrossRepository'] = 'false'
784
785	def _createRepositoryIfNeeded(self, quiet=False, verbose=False):
786		"""create/update repository"""
787		if self.repository:
788			return
789		self.repository = Repository(self.treePath,
790			self.outputDirectory, self.repositoryPath,
791			self.packagesPath, self.shellVariables, self.policy,
792			self.options.preserveFlags, quiet, verbose)
793
794	def _updatePortsTree(self):
795		"""Get/Update the port tree via git"""
796		print('Refreshing the port tree: %s' % self.treePath)
797		ensureCommandIsAvailable('git')
798		if os.path.exists(self.treePath + '/.git'):
799			check_call(['git', 'pull'], cwd=self.treePath)
800		else:
801			check_call(['git', 'clone', haikuportsRepoUrl, self.treePath])
802
803	def _splitPortSpecIntoNameVersionAndRevision(self, portSpecString):
804		elements = portSpecString.split('-')
805		if len(elements) < 1 or len(elements) > 3:
806			sysExit('Invalid port specifier ' + portSpecString)
807
808		return	{
809			'specifier': portSpecString,
810			'name': elements[0],
811			'version': elements[1] if len(elements) > 1 else None,
812			'revision': elements[2] if len(elements) > 2 else None,
813		}
814
815	def _getCategory(self, portName):
816		"""Find location of the specified port in the HaikuPorts tree"""
817		hierarchy = []
818		dirList = os.listdir(self.treePath)
819		for item in dirList:
820			if os.path.isdir(item) and item[0] != '.' and '-' in item:
821				subdirList = os.listdir(item)
822				# remove items starting with '.'
823				subdirList.sort()
824				while subdirList[0][0] == '.':
825					del subdirList[0]
826
827				# locate port
828				try:
829					if subdirList.index(portName) >= 0:
830						# port was found in the category specified by 'item'
831						return item
832				except ValueError:
833					pass
834				hierarchy.append([item, subdirList])
835		return None
836
837	def _checkSourceTree(self, portArgument):
838		if portArgument:
839			info('Checking ports of: ' + portArgument)
840
841			allPorts = self.repository.allPorts
842			portVersionsByName = self.repository.portVersionsByName
843
844			if portArgument in allPorts:
845				# Full port name / ver
846				port = allPorts[portArgument]
847				print('%s	[%s]' % (portArgument, port.category))
848				port.validateRecipeFile(True) # exit 1 if fail
849				return
850			elif portArgument in portVersionsByName:
851				# Base port name
852				somethingFailed = False
853				for version in portVersionsByName[portArgument]:
854					portID = portArgument + '-' + version
855					port = allPorts[portID]
856					print('%s	[%s]' % (portID, port.category))
857					try:
858						port.validateRecipeFile(True)
859					except SystemExit as e:
860						somethingFailed = True
861						print(e.code)
862				if somethingFailed:
863					sys.exit(1)
864			else:
865				# Unknown
866				sysExit('%s is not a known port!' % portArgument)
867
868		else:
869			info('Checking HaikuPorts tree at: ' + self.treePath)
870			allPorts = self.repository.allPorts
871			portVersionsByName = self.repository.portVersionsByName
872			somethingFailed = False
873			for portName in sorted(portVersionsByName.keys(), key=str.lower):
874				for version in portVersionsByName[portName]:
875					portID = portName + '-' + version
876					port = allPorts[portID]
877					print('%s	[%s]' % (portID, port.category))
878					try:
879						port.validateRecipeFile(True)
880					except SystemExit as e:
881						print(e.code)
882						somethingFailed = True
883			if somethingFailed:
884				sys.exit(1)
885
886	def _checkFormatVersions(self):
887		# Read the format versions used by the tree and stop if they don't
888		# match the ones supported by this instance of haikuporter.
889		formatVersionsFile = self.treePath + '/FormatVersions'
890		recipeFormatVersion = 0
891		if os.path.exists(formatVersionsFile):
892			with open(formatVersionsFile, 'r') as f:
893				formatVersions = f.read()
894			recipeFormatVersionMatch = re.search('^RecipeFormatVersion=(.+?)$',
895												 formatVersions,
896												 flags=re.MULTILINE)
897			if recipeFormatVersionMatch:
898				try:
899					recipeFormatVersion = int(recipeFormatVersionMatch.group(1))
900				except ValueError:
901					pass
902
903		if recipeFormatVersion > getRecipeFormatVersion():
904			sysExit('The version of the recipe file format used in the ports '
905					'tree is newer than the one supported by haikuporter.\n'
906					'Please upgrade haikuporter.')
907		if recipeFormatVersion < getRecipeFormatVersion():
908			sysExit('The version of the recipe file format used in the ports '
909					'tree is older than the one supported by haikuporter.\n'
910					'Please upgrade the ports tree.')
911
912	def _checkPortsReleases(self, portArgument):
913		self._createRepositoryIfNeeded(True)
914		if portArgument:
915			print('Checking for newer release for port: ' + portArgument)
916
917			allPorts = self.repository.allPorts
918			portVersionsByName = self.repository.portVersionsByName
919
920			if portArgument in allPorts:
921				# Full port name / ver
922				port = allPorts[portArgument]
923				print('%s	[%s]' % (portArgument, port.category))
924				port.checkPortReleases()
925				return
926			elif portArgument in portVersionsByName:
927				# Base port name
928				version = self.repository.getActiveVersionOf(portArgument)
929				if not version:
930					sysExit('%s does not have an active version!' % portArgument)
931				portID = portArgument + '-' + version
932				port = allPorts[portID]
933				print('%s	[%s]' % (portID, port.category))
934				port.checkPortReleases()
935			else:
936				# Unknown
937				sysExit('%s is not a known port!' % portArgument)
938
939		else:
940			print('Checking for newer release for ports from tree at: ' + self.treePath)
941			allPorts = self.repository.allPorts
942			portVersionsByName = self.repository.portVersionsByName
943			somethingFailed = False
944			for portName in sorted(portVersionsByName.keys(), key=str.lower):
945				version = self.repository.getActiveVersionOf(portName)
946				if not version:
947					continue
948				portID = portName + '-' + version
949				port = allPorts[portID]
950				print('%s	[%s]' % (portID, port.category))
951				port.checkPortReleases()
952