Skip to content

[build support] Generic prefixed binary lookup function in swift_build_support #1586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions utils/build-script
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ from SwiftBuildSupport import (

sys.path.append(os.path.join(os.path.dirname(__file__), 'swift_build_support'))

import swift_build_support.clang # noqa (E402 module level import not at top of file)
import swift_build_support.cmake # noqa (E402)
import swift_build_support.debug # noqa (E402)
# E402 means module level import not at top of file
import swift_build_support.toolchain # noqa (E402)
import swift_build_support.cmake # noqa (E402)
import swift_build_support.debug # noqa (E402)
from swift_build_support.migration import migrate_impl_args # noqa (E402)
import swift_build_support.ninja # noqa (E402)
import swift_build_support.tar # noqa (E402)
import swift_build_support.targets # noqa (E402)
import swift_build_support.ninja # noqa (E402)
import swift_build_support.tar # noqa (E402)
import swift_build_support.targets # noqa (E402)


# Main entry point for the preset mode.
Expand Down Expand Up @@ -1018,7 +1019,7 @@ the number of parallel build jobs to use""",
if args.clean and os.path.isdir(build_dir):
shutil.rmtree(build_dir)

host_clang = swift_build_support.clang.host_clang(
host_clang = swift_build_support.toolchain.host_clang(
xcrun_toolchain=args.darwin_xcrun_toolchain)
if not host_clang:
print_with_argv0(
Expand Down
90 changes: 0 additions & 90 deletions utils/swift_build_support/swift_build_support/clang.py

This file was deleted.

131 changes: 131 additions & 0 deletions utils/swift_build_support/swift_build_support/toolchain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# -*- python -*-
# swift_build_support/toolchain.py - Detect host machine's versioned
# executables
#
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
#
# ----------------------------------------------------------------------------
#
# Find the path to a Clang executable on the host machine that is most
# suitable for building Swift.
#
# ----------------------------------------------------------------------------

from __future__ import absolute_import

import platform
import subprocess

from . import xcrun
from .which import which


class Toolchain(object):
"""
A class that stores accessible paths to system commands.

It requires a 'cc' and 'cxx' tool in its keyword args.
"""
def __init__(self, cc=None, cxx=None, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry if I'm not picking up on something here, but why provide default arguments for arguments the documentation claims are necessary? If you're looking for the ability to use named arguments like Toolchain(cc="foo", cxx="bar"), you can do that without supplying default arguments in Python.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured it was good to express that these arguments are strictly required, though the same could be expressed with

assert(tools.get('cc') is not None)

self.cc = cc
self.cxx = cxx
self.tools = [cc, cxx]
for tool, path in kwargs.iteritems():
self.tools.append(path)
setattr(self, tool, path)


def _freebsd_release_date():
"""
Return the release date for FreeBSD operating system on this host.
If the release date cannot be ascertained, return None.
"""
try:
# For details on `sysctl`, see:
# http://www.freebsd.org/cgi/man.cgi?sysctl(8)
return int(subprocess.check_output(
['sysctl', '-n', 'kern.osreldate']).rstrip())
except subprocess.CalledProcessError:
return None


def _first_common_toolchain(tools, suffixes=None):
"""
Return a Toolchain of resolved paths where each path has
the same suffix.

If there is no common version of all binaries found, return None.
"""
if suffixes is None:
# No suffixes provided, default to using empty suffix only
suffixes = ['']

for suffix in suffixes:
path_map = dict()
for name, tool in tools.iteritems():
path = which(tool + suffix)
if not path:
break
path_map[name] = path
if len(path_map) == len(tools):
return Toolchain(**path_map)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, is this why default arguments are provided above? A bit too much metaprogramming for me, but to each their own! 😅

return None


def host_toolchain(xcrun_toolchain='default', tools=None, suffixes=None):
"""
Return a Toolchain with the first available versions of all
specified tools, plus clang and clang++, searching in the order of the
given suffixes.

If no matching executables are found, return None.
"""
if tools is None:
tools = dict()

# Require cc and cxx for a toolchain; this ensures whatever llvm tool we
# find matches the version of clang returned by a previous run of
# this tool
tools['cc'] = 'clang'
tools['cxx'] = 'clang++'

if platform.system() == 'Darwin':
# Only use xcrun on Darwin
path_map = {}
for name, tool in tools.iteritems():
path = xcrun.find(xcrun_toolchain, tool)
if not path:
return None
path_map[name] = path
return Toolchain(**path_map)
else:
return _first_common_toolchain(tools, suffixes=suffixes)


def host_clang(xcrun_toolchain):
"""
Return a Toolchain for the host platform.
If no appropriate compilers can be found, return None.
"""
if platform.system() == 'FreeBSD':
# See: https://github.com/apple/swift/pull/169
# Building Swift from source requires a recent version of the Clang
# compiler with C++14 support.
freebsd_release_date = _freebsd_release_date()
if freebsd_release_date and freebsd_release_date >= 1100000:
# On newer releases of FreeBSD, the default Clang is sufficient.
return host_toolchain(xcrun_toolchain)
else:
# On older releases, or on releases for which we cannot determine
# the release date, we search for the most modern version
# available.
return host_toolchain(xcrun_toolchain,
suffixes=['38', '37', '36', '35'])
return host_toolchain(xcrun_toolchain,
suffixes=['-3.8', '-3.7', '-3.6', '-3.5'])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suffixes should contain '', as before

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, you're right. Thanks for catching that! Fixed in master.

31 changes: 0 additions & 31 deletions utils/swift_build_support/tests/test_clang.py

This file was deleted.

62 changes: 62 additions & 0 deletions utils/swift_build_support/tests/test_toolchain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# -*- python -*-
# test_toolchain.py - Unit tests for swift_build_support.toolchain
#
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors

import os
import platform
import unittest

from swift_build_support.toolchain import host_clang, host_toolchain


class HostClangTestCase(unittest.TestCase):
def test_clang_available_on_this_platform(self):
# Test that Clang is installed on this platform, as a means of
# testing host_clang().
clang = host_clang(xcrun_toolchain='default')

# The CC and CXX from host_clang() should be of the form
# 'path/to/clang', where 'clang' may have a trailing version
# number.
self.assertTrue(os.path.split(clang.cc)[-1].startswith('clang'))
self.assertTrue(os.path.split(clang.cxx)[-1].startswith('clang++'))


class HostToolchainTestCase(unittest.TestCase):
def test_found_executables_match(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit-pick: This doesn't test host_clang(), it tests host_toolchain(). As such, I'd recommend putting it in a separate class HostToolchainTestCase(unittest.TestCase).

# Test that the raw incovation of _first_common_executables
# either returns None or matching paths.
suffixes = ['', '-3.8', '-3.7', '-3.6']
toolchain = host_toolchain(suffixes=suffixes)
self.assertTrue(len(toolchain.tools) == 2)

exec_names = {'foo': 'a-tool-that-does-not-exist'}
toolchain = host_toolchain(tools=exec_names,
suffixes=suffixes)
self.assertIsNone(toolchain)

@unittest.skipUnless(platform.system() == 'Darwin',
'llvm-cov is only guaranteed to exist on OS X')
def test_can_find_llvm_cov(self):
suffixes = ['', '-3.8', '-3.7', '-3.6']
exec_names = {'llvm_cov': 'llvm-cov'}
toolchain = host_toolchain(tools=exec_names, suffixes=suffixes)

# must have clang, clang++, and llvm-cov
self.assertTrue(len(toolchain.tools) == 3)

try:
toolchain.llvm_cov
except AttributeError:
self.fail("toolchain does not have llvm_cov")


if __name__ == '__main__':
unittest.main()