quiet = False
command = None
dry_run = False
+ use_git_reference = True
trees = Trees()
globals = Globals()
def __exit__(self, type, value, traceback):
os.chdir(self.cwd)
-#
-# Version
-#
-
-class Version:
- def __init__(self, s):
- self.devel = False
-
- if s.startswith("'"):
- s = s[1:]
- if s.endswith("'"):
- s = s[0:-1]
-
- if s.endswith('devel'):
- s = s[0:-5]
- self.devel = True
-
- if s.endswith('pre'):
- s = s[0:-3]
-
- p = s.split('.')
- self.major = int(p[0])
- self.minor = int(p[1])
- if len(p) == 3:
- self.micro = int(p[2])
- else:
- self.micro = 0
-
- @classmethod
- def from_git_tag(cls, tag):
- bits = tag.split('-')
- c = cls(bits[0])
- if len(bits) > 1 and int(bits[1]) > 0:
- c.devel = True
- return c
-
- def bump_minor(self):
- self.minor += 1
- self.micro = 0
-
- def bump_micro(self):
- self.micro += 1
-
- def to_devel(self):
- self.devel = True
-
- def to_release(self):
- self.devel = False
-
- def __str__(self):
- s = '%d.%d.%d' % (self.major, self.minor, self.micro)
- if self.devel:
- s += 'devel'
-
- return s
#
# Targets
"""
- def __init__(self, platform, directory=None):
+ def __init__(self, platform, directory=None, dependencies_only=False):
"""
platform -- platform string (e.g. 'windows', 'linux', 'osx')
directory -- directory to work in; if None we will use a temporary directory
# True to build our dependencies ourselves; False if this is taken care
# of in some other way
self.build_dependencies = True
+ self.dependencies_only = dependencies_only
if directory is None:
+ try:
+ os.makedirs(config.get('temp'))
+ except OSError as e:
+ if e.errno != 17:
+ raise e
self.directory = tempfile.mkdtemp('', 'tmp', config.get('temp'))
self.rmdir = True
self.set('CCACHE_BASEDIR', os.path.realpath(self.directory))
"""
Call package() in the cscript and return what it returns, except that
anything not in a list will be put into one.
+ options: from command line
"""
if len(inspect.getfullargspec(tree.cscript['package']).args) == 3:
- packages = tree.call('package', tree.version, options)
+ packages = tree.call('package', tree.version, tree.add_defaults(options))
else:
log_normal("Deprecated cscript package() method with no options parameter")
packages = tree.call('package', tree.version)
copyfile(p, os.path.join(output_dir, os.path.basename(devel_to_git(tree.commit, p))))
def package(self, project, checkout, output_dir, options, notarize):
+ """
+ options: from command line
+ """
tree = self.build(project, checkout, options, for_package=True)
- tree.add_defaults(options)
p = self._cscript_package(tree, options)
self._copy_packages(tree, p, output_dir)
tree = globals.trees.get(project, checkout, self)
if self.build_dependencies:
tree.build_dependencies(options)
- tree.build(options, for_package=for_package)
+ if not self.dependencies_only:
+ tree.build(options, for_package=for_package)
return tree
def test(self, project, checkout, target, test, options):
"""test is the test case to run, or None"""
tree = globals.trees.get(project, checkout, target)
- tree.add_defaults(options)
with TreeDirectory(tree):
if len(inspect.getfullargspec(tree.cscript['test']).args) == 3:
- return tree.call('test', options, test)
+ return tree.call('test', tree.add_defaults(options), test)
else:
log_normal('Deprecated cscript test() method with no options parameter')
return tree.call('test', test)
self.mounts.append(m)
-class FlatpakTarget(Target):
- def __init__(self, project, checkout):
- super(FlatpakTarget, self).__init__('flatpak')
- self.build_dependencies = False
- self.project = project
- self.checkout = checkout
-
- def setup(self):
- pass
-
- def command(self, cmd):
- command(cmd)
-
- def checkout_dependencies(self):
- tree = globals.trees.get(self.project, self.checkout, self)
- return tree.checkout_dependencies()
-
- def flatpak(self):
- return 'flatpak'
-
- def flatpak_builder(self):
- b = 'flatpak-builder'
- if config.has('flatpak_state_dir'):
- b += ' --state-dir=%s' % config.get('flatpak_state_dir')
- return b
-
-
class WindowsDockerTarget(DockerTarget):
"""
This target exposes the following additional API:
- version: Windows version ('xp' or None)
bits: bitness of Windows (32 or 64)
name: name of our target e.g. x86_64-w64-mingw32.shared
environment_prefix: path to Windows environment for the appropriate target (libraries and some tools)
tool_path: path to 32- and 64-bit tools
"""
- def __init__(self, windows_version, bits, directory, environment_version):
+ def __init__(self, bits, directory, environment_version):
super(WindowsDockerTarget, self).__init__('windows', directory)
- self.version = windows_version
self.bits = bits
+ # This was used to differentiate "normal" Windows from XP, and is no longer important,
+ # but old cscripts still look for it
+ self.version = None
self.tool_path = '%s/usr/bin' % config.get('mxe_prefix')
if self.bits == 32:
"""
This target exposes the following additional API:
- version: Windows version ('xp' or None)
bits: bitness of Windows (32 or 64)
name: name of our target e.g. x86_64-w64-mingw32.shared
environment_prefix: path to Windows environment for the appropriate target (libraries and some tools)
"""
def __init__(self, directory):
super().__init__('windows', directory)
- self.version = None
self.bits = 64
- self.environment_prefix = config.get('windows_native_environmnet_prefix')
+ self.environment_prefix = config.get('windows_native_environment_prefix')
self.set('PATH', '%s/bin:%s' % (self.environment_prefix, os.environ['PATH']))
self.privileged = True
+class FlatpakTarget(Target):
+ def __init__(self, project, checkout, work):
+ super(FlatpakTarget, self).__init__('flatpak')
+ self.build_dependencies = False
+ self.project = project
+ self.checkout = checkout
+ # If we use git references we end up with a checkout in one mount trying
+ # to link to the git reference repo in other, which doesn't work.
+ globals.use_git_reference = False
+ if config.has('flatpak_state_dir'):
+ self.mount(config.get('flatpak_state_dir'))
+
+ def command(self, c):
+ log_normal('host -> %s' % c)
+ command('%s %s' % (self.variables_string(), c))
+
+ def setup(self):
+ super().setup()
+ globals.trees.get(self.project, self.checkout, self).checkout_dependencies()
+
+ def flatpak(self):
+ return 'flatpak'
+
+ def flatpak_builder(self):
+ b = 'flatpak-builder'
+ if config.has('flatpak_state_dir'):
+ b += ' --state-dir=%s' % config.get('flatpak_state_dir')
+ return b
+
+
def notarize_dmg(dmg):
p = subprocess.run(
config.get('osx_notarytool') + [
class OSXTarget(Target):
- def __init__(self, directory=None):
+ def __init__(self, directory=None, environment_version=None):
super(OSXTarget, self).__init__('osx', directory)
self.sdk_prefix = config.get('osx_sdk_prefix')
self.environment_prefix = config.get('osx_environment_prefix')
+ if environment_version:
+ self.environment_prefix += '_%s' % environment_version
self.apple_id = config.get('apple_id')
self.apple_password = config.get('apple_password')
self.osx_keychain_file = config.get('osx_keychain_file')
def unlock_keychain(self):
self.command('security unlock-keychain -p %s %s' % (self.osx_keychain_password, self.osx_keychain_file))
+ def package(self, project, checkout, output_dir, options, notarize):
+ self.unlock_keychain()
+ tree = globals.trees.get(project, checkout, self)
+ with TreeDirectory(tree):
+ p = self._cscript_package_and_notarize(tree, options, self.can_notarize and notarize)
+ self._copy_packages(tree, p, output_dir)
+
def _copy_packages(self, tree, packages, output_dir):
for p in packages:
dest = os.path.join(output_dir, os.path.basename(devel_to_git(tree.commit, p)))
class OSXSingleTarget(OSXTarget):
- def __init__(self, arch, sdk, deployment, directory=None, can_notarize=True):
- super(OSXSingleTarget, self).__init__(directory)
+ def __init__(self, arch, sdk, deployment, directory=None, can_notarize=True, environment_version=None):
+ super(OSXSingleTarget, self).__init__(directory=directory, environment_version=environment_version)
self.arch = arch
self.sdk = sdk
self.deployment = deployment
flags = '-isysroot %s/MacOSX%s.sdk -arch %s' % (self.sdk_prefix, sdk, arch)
if arch == 'x86_64':
- host_enviro = '%s/x86_64/%s' % (config.get('osx_environment_prefix'), deployment)
+ host_enviro = '%s/x86_64/%s' % (self.environment_prefix, deployment)
else:
- host_enviro = '%s/x86_64/10.10' % config.get('osx_environment_prefix')
- target_enviro = '%s/%s/%s' % (config.get('osx_environment_prefix'), arch, deployment)
+ host_enviro = '%s/x86_64/10.10' % self.environment_prefix
+ target_enviro = '%s/%s/%s' % (self.environment_prefix, arch, deployment)
self.bin = '%s/bin' % target_enviro
def package(self, project, checkout, output_dir, options, notarize):
tree = self.build(project, checkout, options, for_package=True)
- tree.add_defaults(options)
- self.unlock_keychain()
- p = self._cscript_package_and_notarize(tree, options, self.can_notarize and notarize)
- self._copy_packages(tree, p, output_dir)
+ super().package(project, checkout, output_dir, options, notarize)
class OSXUniversalTarget(OSXTarget):
- def __init__(self, directory=None):
- super(OSXUniversalTarget, self).__init__(directory)
+ def __init__(self, directory=None, environment_version=None):
+ super(OSXUniversalTarget, self).__init__(directory=directory, environment_version=environment_version)
self.sdk = config.get('osx_sdk')
self.sub_targets = []
for arch, deployment in (('x86_64', config.get('osx_intel_deployment')), ('arm64', config.get('osx_arm_deployment'))):
- target = OSXSingleTarget(arch, self.sdk, deployment, os.path.join(self.directory, arch, deployment))
+ target = OSXSingleTarget(arch, self.sdk, deployment, os.path.join(self.directory, arch, deployment), environment_version=environment_version)
target.ccache = self.ccache
self.sub_targets.append(target)
+ self.can_notarize = True
def package(self, project, checkout, output_dir, options, notarize):
for target in self.sub_targets:
tree.build_dependencies(options)
tree.build(options, for_package=True)
- self.unlock_keychain()
- tree = globals.trees.get(project, checkout, self)
- with TreeDirectory(tree):
- p = self._cscript_package_and_notarize(tree, options, notarize)
- self._copy_packages(tree, p, output_dir)
+ super().package(project, checkout, output_dir, options, notarize)
+
+ @Target.ccache.setter
+ def ccache(self, v):
+ for target in self.sub_targets:
+ target.ccache = v
+
class SourceTarget(Target):
"""Build a source .tar.bz2 and .zst"""
zstd = os.path.abspath('%s-%s.tar.zst' % (name, tree.version))
copyfile(zstd, os.path.join(output_dir, os.path.basename(devel_to_git(tree.commit, zstd))))
+
+class LocalTarget(Target):
+ """Build on the local machine with its environment"""
+ def __init__(self, work, dependencies_only=False):
+ super(LocalTarget, self).__init__('linux', work, dependencies_only=dependencies_only)
+ # Hack around ffmpeg.git which sees that the target isn't windows/osx and then assumes
+ # distro will be there.
+ self.distro = None
+ self.detail = None
+ self.bits = 64
+ self.set('PKG_CONFIG_PATH', '%s/lib/pkgconfig:%s/bin/pkgconfig' % (self.directory, self.directory))
+ self.append_with_colon('LD_LIBRARY_PATH', '%s/lib' % self.directory)
+ self.set('CXXFLAGS', '-I%s/include' % self.directory)
+ self.set('LINKFLAGS', '-L%s/lib' % self.directory)
+
+ def command(self, c):
+ log_normal('host -> %s' % c)
+ command('%s %s' % (self.variables_string(), c))
+
+ def cleanup(self):
+ rmtree(self.directory)
+
# @param s Target string:
# windows-{32,64}
# or ubuntu-version-{32,64}
x = s.split('-')
if platform.system() == "Windows":
target = WindowsNativeTarget(args.work)
+ elif len(x) == 2:
+ target = WindowsDockerTarget(int(x[1]), args.work, args.environment_version)
else:
- if len(x) == 2:
- target = WindowsDockerTarget(None, int(x[1]), args.work, args.environment_version)
- elif len(x) == 3:
- target = WindowsDockerTarget(x[1], int(x[2]), args.work, args.environment_version)
- else:
- raise Error("Bad Windows target name `%s'")
+ raise Error("Bad Windows target name `%s'")
elif s.startswith('ubuntu-') or s.startswith('debian-') or s.startswith('centos-') or s.startswith('fedora-') or s.startswith('mageia-'):
p = s.split('-')
if len(p) != 3:
elif s == 'raspbian':
target = LinuxTarget(s, None, None, args.work)
elif s == 'osx':
- target = OSXUniversalTarget(args.work)
+ target = OSXUniversalTarget(args.work, environment_version=args.environment_version)
elif s == 'osx-intel':
- target = OSXSingleTarget('x86_64', config.get('osx_sdk'), config.get('osx_intel_deployment'), args.work)
+ target = OSXSingleTarget('x86_64', config.get('osx_sdk'), config.get('osx_intel_deployment'), args.work, environment_version=args.environment_version)
elif s == 'osx-old':
- target = OSXSingleTarget('x86_64', config.get('osx_sdk'), config.get('osx_old_deployment'), args.work, False)
+ target = OSXSingleTarget('x86_64', config.get('osx_sdk'), config.get('osx_old_deployment'), args.work, False, environment_version=args.environment_version)
elif s == 'source':
target = SourceTarget()
elif s == 'flatpak':
- target = FlatpakTarget(args.project, args.checkout)
+ target = FlatpakTarget(args.project, args.checkout, args.work)
elif s == 'appimage':
target = AppImageTarget(args.work)
+ elif s == 'local':
+ target = LocalTarget(args.work, args.dependencies_only)
if target is None:
raise Error("Bad target `%s'" % s)
if globals.quiet:
flags = '-q'
redirect = '>/dev/null'
- if config.has('git_reference'):
+ if config.has('git_reference') and globals.use_git_reference:
ref = '--reference-if-able %s/%s.git' % (config.get('git_reference'), self.name)
else:
ref = ''
urls = command_and_read('git config --file .gitmodules --get-regexp url')
for path, url in zip(paths, urls):
ref = ''
- if config.has('git_reference'):
+ if config.has('git_reference') and globals.use_git_reference:
url = url.split(' ')[1]
ref_path = os.path.join(config.get('git_reference'), os.path.basename(url))
if os.path.exists(ref_path):
path = path.split(' ')[1]
command('git -c protocol.file.allow=always submodule --quiet update %s %s' % (ref, path))
- if os.path.exists('%s/wscript' % proj):
- v = read_wscript_variable(proj, "VERSION");
- if v is not None:
- try:
- self.version = Version(v)
- except:
- try:
- tag = command_and_read('git -C %s describe --match v* --tags' % proj)[0][1:]
- self.version = Version.from_git_tag(tag)
- except:
- # We'll leave version as None if we can't read it; maybe this is a bad idea
- # Should probably just install git on the Windows VM
- pass
+ head_tag = command_and_read(f'git -C {proj} tag -l --points-at HEAD')
+ if head_tag:
+ self.version = head_tag[0][1:]
+ else:
+ self.version = command_and_read(f'git -C {proj} rev-parse --short HEAD')[0]
os.chdir(cwd)
return self.cscript[function](self.target, *args)
def add_defaults(self, options):
- """Add the defaults from self into a dict options"""
+ """Add the defaults from self into a dict options and returns a new dict"""
+ new_options = copy.copy(options)
if 'option_defaults' in self.cscript:
from_cscript = self.cscript['option_defaults']
if isinstance(from_cscript, dict):
log_normal("Deprecated cscript option_defaults method; replace with a dict")
defaults_dict = from_cscript()
for k, v in defaults_dict.items():
- if not k in options:
- options[k] = v
+ if not k in new_options:
+ new_options[k] = v
+ return new_options
def dependencies(self, options):
"""
yield details of the dependencies of this tree. Each dependency is returned
- as a tuple of (tree, options, parent_tree). The 'options' parameter are the options that
- we want to force for 'self'.
+ as a tuple of (tree, options).
+ options: either from command line (for top-level tree) or from parent's dependencies() (for other trees)
"""
if not 'dependencies' in self.cscript:
return
if len(inspect.getfullargspec(self.cscript['dependencies']).args) == 2:
- self_options = copy.copy(options)
- self.add_defaults(self_options)
- deps = self.call('dependencies', self_options)
+ deps = self.call('dependencies', self.add_defaults(options))
else:
log_normal("Deprecated cscript dependencies() method with no options parameter")
deps = self.call('dependencies')
dep_options = d[2] if len(d) > 2 else {}
for i in dep.dependencies(dep_options):
yield i
- yield (dep, dep_options, self)
+ yield (dep, dep_options)
def checkout_dependencies(self, options={}):
for i in self.dependencies(options):
def build_dependencies(self, options):
"""
Called on the 'main' project tree (-p on the command line) to build all dependencies.
- 'options' will be the ones from the command line.
+ options: either from command line (for top-level tree) or from parent's dependencies() (for other trees)
"""
- for i in self.dependencies(options):
- i[0].build(i[1])
+ for dependency, dependency_options in self.dependencies(options):
+ dependency.build(dependency_options)
def build(self, options, for_package=False):
+ """
+ options: either from command line (for top-level tree) or from parent's dependencies() (for other trees)
+ """
if self.built:
return
- log_verbose("Building %s %s %s with %s" % (self.name, self.commit_ish, self.version, options))
+ log_verbose("Building %s %s %s with %s" % (self.name, self.commit_ish, self.version, self.add_defaults(options)))
variables = copy.copy(self.target.variables)
- options = copy.copy(options)
- self.add_defaults(options)
-
if not globals.dry_run:
num_args = len(inspect.getfullargspec(self.cscript['build']).args)
if num_args == 3:
- self.call('build', options, for_package)
+ self.call('build', self.add_defaults(options), for_package)
elif num_args == 2:
- self.call('build', options)
+ self.call('build', self.add_defaults(options))
else:
self.call('build')
subparsers = parser.add_subparsers(help='command to run', dest='command')
parser_build = subparsers.add_parser("build", help="build project")
+ parser_build.add_argument('--dependencies-only', help='only build dependencies', action='store_true')
parser_package = subparsers.add_parser("package", help="build and package project")
parser_package.add_argument('--no-notarize', help='do not notarize .dmg packages', action='store_true')
parser_release = subparsers.add_parser("release", help="release a project using its next version number (adding a tag)")
if target is not None and not args.keep:
target.cleanup()
- elif args.command == 'release':
- if args.minor is False and args.micro is False:
- raise Error('you must specify --minor or --micro')
-
- target = SourceTarget()
- tree = globals.trees.get(args.project, args.checkout, target)
-
- version = tree.version
- version.to_release()
- if args.minor:
- version.bump_minor()
- else:
- version.bump_micro()
-
- with TreeDirectory(tree):
- command('git tag -m "v%s" v%s' % (version, version))
- command('git push --tags')
-
- target.cleanup()
-
elif args.command == 'pot':
target = SourceTarget()
tree = globals.trees.get(args.project, args.checkout, target)
if len(s) > 1:
t = s[1]
if len(t) > 0 and t[0] == 'v':
- v = Version(t[1:])
- if (args.major is None or v.major == args.major) and (args.minor is None or v.minor == args.minor):
+ v = t[1:].split('.')
+ if (args.major is None or int(v[0]) == args.major) and (args.minor is None or int(v[1]) == args.minor):
latest = v
- print(latest)
+ print('.'.join(latest))
target.cleanup()
elif args.command == 'test':
shutil.copytree('.', args.output)
target.cleanup()
- elif args.command == 'dependencies':
- if args.target is None:
- raise Error('you must specify -t or --target')
- if args.checkout is None:
- raise Error('you must specify -c or --checkout')
-
- target = target_factory(args)
- tree = globals.trees.get(args.project, args.checkout, target)
- print("strict digraph {")
- for d in list(tree.dependencies({})):
- print("%s -> %s;" % (d[2].name.replace("-", "-"), d[0].name.replace("-", "_")))
- print("}")
-
elif args.command == 'notarize':
if args.dmgs is None:
raise Error('you must specify ---dmgs')