From 9552adf7e50916bf13e0c505a470f1611c0b391b Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Sat, 24 Apr 2021 02:42:53 +0300 Subject: [PATCH] Versioned Clang automatic configuration (#1) * Versioned Clang automatic configuration Currently, without annoying `toolset.using` directives in `user-config.jam`, `b2 toolset=clang-xx` silently uses clang++ binary, even if it is a different version than requested. Instead of copycating GCC or reinventing a wheel I have generalized GCC automatic configuration and used it for Clang. --- .cirrus.yml | 1 - src/build/version.jam | 59 +++++++++++++++++ src/tools/clang-darwin.jam | 28 ++++++--- src/tools/clang-linux.jam | 24 ++++--- src/tools/common.jam | 73 +++++++++++++++++++++ src/tools/gcc.jam | 126 +++++++++++-------------------------- src/util/numbers.jam | 23 +++++++ 7 files changed, 226 insertions(+), 108 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index c5113ac334..0099cbfd7f 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -16,7 +16,6 @@ freebsd_task: - { name: 'FreeBSD, Clang 9', env: { TOOLSET: clang, TEST_TOOLSET: clang, CXX: 'clang++90', PACKAGE: 'llvm90' }, freebsd_instance: { image_family: 'freebsd-12-2' } } - { name: 'FreeBSD, Clang 8', env: { TOOLSET: clang, TEST_TOOLSET: clang, CXX: 'clang++80', PACKAGE: 'llvm80' }, freebsd_instance: { image_family: 'freebsd-12-2' } } - { name: 'FreeBSD, Clang 7', env: { TOOLSET: clang, TEST_TOOLSET: clang, CXX: 'clang++70', PACKAGE: 'llvm70' }, freebsd_instance: { image_family: 'freebsd-12-2' } } - - { name: 'FreeBSD, Clang 6', env: { TOOLSET: clang, TEST_TOOLSET: clang, CXX: 'clang++60', PACKAGE: 'llvm60' }, freebsd_instance: { image_family: 'freebsd-12-2' } } # To install with ports we need to initialize the package manager. To avoid # confirmation prompts we need to set an env var. install_script: [ diff --git a/src/build/version.jam b/src/build/version.jam index fa59097872..b621f56310 100644 --- a/src/build/version.jam +++ b/src/build/version.jam @@ -1,3 +1,4 @@ +# Copyright 2021 Nikita Kniazev # Copyright 2002, 2003, 2004, 2006 Vladimir Prus # Copyright 2008, 2012 Jurko Gospodnetic # Distributed under the Boost Software License, Version 1.0. @@ -99,6 +100,34 @@ rule version-less ( lhs + : rhs + ) return $(result) ; } +# Returns "true" if the required version is compatible with the having one. +# This uses sematic versioning where (major.x.y) is compatible with +# (major.n.m) and (major.x.z). And is incompatible for other values. +# +rule version-compatible ( req + : has + ) +{ + numbers.check $(req) ; + numbers.check $(has) ; + + if $(req) = $(has) + { + return true ; + } + + while $(req) && [ numbers.equal $(req[1]) $(has[1]:E=0) ] + { + req = $(req[2-]) ; + has = $(has[2-]) ; + } + + if $(req) + { + return ; + } + + return true ; +} + # Returns "true" if the current JAM version version is at least the given # version. @@ -163,4 +192,34 @@ rule __test__ ( ) assert.false version-less 03 1 10 0 : 3 1 10 0 0 ; # TODO: Add tests for invalid input data being sent to version-less. + + + assert.true version-compatible 4 : 4 ; + assert.true version-compatible 4 : 4 9 ; + assert.false version-compatible 4 9 : 4 ; + assert.true version-compatible 4 9 : 4 9 ; + assert.false version-compatible 4 9 1 : 4 9 ; + assert.true version-compatible 4 9 1 : 4 9 1 ; + assert.false version-compatible 4 8 : 4 9 ; + assert.false version-compatible 4 8 1 : 4 9 ; + assert.false version-compatible 4 8 1 : 4 9 1 ; + assert.true version-compatible 5 : 5 ; + assert.false version-compatible 5 : 4 ; + assert.false version-compatible 5 : 4 9 ; + assert.false version-compatible 5 1 : 5 ; + assert.true version-compatible 5 1 : 5 1 ; + assert.false version-compatible 5 1 : 5 2 ; + assert.false version-compatible 5 1 1 : 5 ; + assert.false version-compatible 5 1 1 : 5 1 ; + assert.false version-compatible 5 2 : 5 ; + assert.false version-compatible 5 2 : 5 1 ; + assert.true version-compatible 5 2 : 5 2 ; + assert.true version-compatible 4 : 4 0 ; + assert.true version-compatible 4 0 : 4 ; + assert.true version-compatible 04 : 4 ; + assert.true version-compatible 04 : 04 ; + assert.true version-compatible 04 : 4 ; + assert.true version-compatible 04 00 : 04 ; + assert.true version-compatible 04 : 04 00 ; } + diff --git a/src/tools/clang-darwin.jam b/src/tools/clang-darwin.jam index 00e8714e5f..659a683ce0 100644 --- a/src/tools/clang-darwin.jam +++ b/src/tools/clang-darwin.jam @@ -1,6 +1,6 @@ # Copyright Vladimir Prus 2004. # Copyright Noel Belcourt 2007. -# Copyright Nikita Kniazev 2020. +# Copyright Nikita Kniazev 2020-2021. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE.txt # or copy at https://www.bfgroup.xyz/b2/LICENSE.txt) @@ -54,15 +54,11 @@ if [ MATCH (--debug-configuration) : [ modules.peek : ARGV ] ] # compile and link options allow you to specify addition command line options for each version rule init ( version ? : command * : options * ) { - command = [ common.get-invocation-command clang-darwin : clang++ - : $(command) : /usr/bin /usr/local/bin ] ; - - # Determine the version - local command-string = $(command:J=" ") ; - if $(command) - { - version ?= [ MATCH "version ([0-9]+[.][0-9]+)" - : [ SHELL "$(command-string) --version" ] ] ; + command = [ common.find-compiler clang-darwin : clang++ : $(version) : $(command) + : /usr/bin /usr/local/bin ] ; + local command-string = [ common.make-command-string $(command) ] ; + if ! $(version) { # ?= operator does not short-circuit + version ?= [ get-short-version $(command-string) ] ; } local condition = [ common.check-init-parameters clang @@ -80,6 +76,18 @@ rule init ( version ? : command * : options * ) toolset.flags clang-darwin.archive .AR $(condition) : $(archiver[1]) ; } +rule get-full-version ( command-string ) +{ + import clang-linux ; + return [ clang-linux.get-full-version $(command-string) ] ; +} + +rule get-short-version ( command-string ) +{ + import clang-linux ; + return [ clang-linux.get-short-version $(command-string) ] ; +} + SPACE = " " ; toolset.flags clang-darwin.compile.m OPTIONS ; diff --git a/src/tools/clang-linux.jam b/src/tools/clang-linux.jam index 86903bdba3..80cd96cfb6 100644 --- a/src/tools/clang-linux.jam +++ b/src/tools/clang-linux.jam @@ -1,3 +1,4 @@ +# Copyright 2021 Nikita Kniazev # Copyright 2020 Rene Rivera # Copyright (c) 2003 Michael Stevens # Copyright (c) 2010-2011 Bryce Lelbach (blelbach@cct.lsu.edu, maintainer) @@ -51,15 +52,10 @@ if [ MATCH (--debug-configuration) : [ modules.peek : ARGV ] ] { } rule init ( version ? : command * : options * ) { - command = [ common.get-invocation-command clang-linux : clang++ - : $(command) ] ; - - # Determine the version - if $(command) { - local command-string = \"$(command)\" ; - command-string = $(command-string:J=" ") ; - version ?= [ MATCH "version ([0-9.]+)" - : [ SHELL "$(command-string) --version" ] ] ; + command = [ common.find-compiler clang-linux : clang++ : $(version) : $(command) ] ; + local command-string = [ common.make-command-string $(command) ] ; + if ! $(version) { # ?= operator does not short-circuit + version ?= [ get-short-version $(command-string) ] ; } local condition = [ common.check-init-parameters clang-linux @@ -110,6 +106,16 @@ rule init ( version ? : command * : options * ) { toolset.flags clang-linux.archive .AR $(condition) : $(archiver[1]) ; } +rule get-full-version ( command-string ) +{ + return [ gcc.get-full-version $(command-string) ] ; +} + +rule get-short-version ( command-string ) +{ + return [ gcc.get-short-version $(command-string) : 4 ] ; +} + ############################################################################### # Flags diff --git a/src/tools/common.jam b/src/tools/common.jam index f5b004f255..705a1986fd 100644 --- a/src/tools/common.jam +++ b/src/tools/common.jam @@ -1082,6 +1082,79 @@ local rule arch-and-model-tag ( name : type ? : property-set ) return $(arch)$(address-model) ; } +# TODO: probably needs to escape '"' in command parts +# TODO: if part does not contain whitespaces it does not require escaping +rule make-command-string ( command + ) +{ + local command-string = \"$(command)\" ; + return $(command-string:J=" ") ; +} + +rule find-compiler ( toolset : tool : version ? : command * : additional-paths * ) +{ + #1): use user-provided command + if $(command) + { + if ! [ get-invocation-command-nodefault $(toolset) : $(tool) + : $(command) : $(additional-paths) ] + { + local command-string = [ make-command-string $(command) ] ; + errors.error toolset $(toolset) "initialization:" + : provided command '$(command-string)' not found + : initialized from [ errors.nearest-user-location ] ; + } + } + #2): enforce user-provided version + else if $(version) + { + command = [ get-invocation-command-nodefault $(toolset) : $(tool)-$(version) + : : $(additional-paths) ] ; + + #2.1) fallback: check whether "$(tool)" reports the requested version + if ! $(command) { # ?= operator does not short-circuit + command ?= [ get-invocation-command-nodefault $(toolset) : $(tool) + : : $(additional-paths) ] ; + } + + if ! $(command) + { + errors.error toolset $(toolset) "initialization:" + : version '$(version)' requested but neither + '$(tool)-$(version)' nor default '$(tool)' found + : initialized from [ errors.nearest-user-location ] ; + } + + import $(toolset) ; + local tool-version = [ $(toolset).get-full-version $(command) ] ; + + import version ; + if ! [ version.version-compatible [ SPLIT_BY_CHARACTERS $(version) : . ] + : [ SPLIT_BY_CHARACTERS $(tool-version) : . ] ] + { + errors.error toolset $(toolset) "initialization:" + : version '$(version)' requested but + '$(tool)-$(version)' not found and version + '$(tool-version:J=.)' of default '$(command)' + does not match + : initialized from [ errors.nearest-user-location ] + ; + } + } + #3) default: no command and no version specified, try using "$(tool)" + else + { + command = [ get-invocation-command-nodefault $(toolset) : $(tool) + : : $(additional-paths) ] ; + if ! $(command) + { + errors.error toolset $(toolset) "initialization:" + : no command provided, default command '$(tool)' not found + : initialized from [ errors.nearest-user-location ] ; + } + } + return $(command) ; +} + rule __test__ ( ) { import assert ; diff --git a/src/tools/gcc.jam b/src/tools/gcc.jam index 580ab434a8..6a46bd6f47 100644 --- a/src/tools/gcc.jam +++ b/src/tools/gcc.jam @@ -1,3 +1,4 @@ +# Copyright 2021 Nikita Kniazev # Copyright 2001 David Abrahams # Copyright 2002-2017 Rene Rivera # Copyright 2002-2003 Vladimir Prus @@ -150,92 +151,13 @@ type.set-generated-target-suffix OBJ : gcc cygwin : o ; # rule init ( version ? : command * : options * : requirement * ) { - #1): use user-provided command - local tool-command = ; - if $(command) - { - tool-command = [ common.get-invocation-command-nodefault gcc : g++ : - $(command) ] ; - if ! $(tool-command) - { - import errors ; - errors.error toolset gcc "initialization:" - : provided command '$(command)' not found - : initialized from [ errors.nearest-user-location ] ; - } - } - #2): enforce user-provided version - else if $(version) - { - tool-command = [ common.get-invocation-command-nodefault gcc : - "g++-$(version[1])" ] ; - - #2.1) fallback: check whether "g++" reports the requested version - if ! $(tool-command) - { - tool-command = [ common.get-invocation-command-nodefault gcc : g++ ] - ; - if $(tool-command) - { - local tool-command-string = \"$(tool-command)\" ; - tool-command-string = $(tool-command-string:J=" ") ; - local tool-version = [ dump-full-version - $(tool-command-string) ] ; - # Permit a match between a two-digit version specified by the - # user (e.g. 4.4) and a 3-digit version reported by gcc. - # Since only two digits are present in the binary name - # anyway, insisting that user specify the 3-digit version - # when configuring B2, while it is not required on - # the command line, would be strange. - local versionl = [ regex.split $(version) "[.]" ] ; - local tool-versionl = [ regex.split $(tool-version) "[.]" ] ; - if ! ( $(versionl[1]) = $(tool-versionl[1]) && - $(versionl[2]:E=$(tool-versionl[2])) = $(tool-versionl[2]) && - $(versionl[3]:E=$(tool-versionl[3])) = $(tool-versionl[3]) ) - { - import errors ; - errors.error toolset gcc "initialization:" - : version '$(version)' requested but - 'g++-$(version)' not found and version - '$(tool-version)' of default '$(tool-command)' - does not match - : initialized from [ errors.nearest-user-location ] - ; - tool-command = ; - } - } - else - { - import errors ; - errors.error toolset gcc "initialization:" - : version '$(version)' requested but neither - 'g++-$(version)' nor default 'g++' found - : initialized from [ errors.nearest-user-location ] ; - } - } - } - #3) default: no command and no version specified, try using "g++" - else - { - tool-command = [ common.get-invocation-command-nodefault gcc : g++ ] ; - if ! $(tool-command) - { - import errors ; - errors.error toolset gcc "initialization:" - : no command provided, default command 'g++' not found - : initialized from [ errors.nearest-user-location ] ; - } - } - - # Information about the gcc command... # The command. - local command = $(tool-command) ; + command = [ common.find-compiler gcc : g++ : $(version) : $(command) ] ; # The 'command' variable can have multiple elements but when calling the # SHELL builtin we need a single string, and we need to quote elements # with spaces. - local command-string = \"$(command)\" ; - command-string = $(command-string:J=" ") ; + local command-string = [ common.make-command-string $(command) ] ; # The root directory of the tool install. local root = [ feature.get-values : $(options) ] ; # The bin directory where to find the command to execute. @@ -259,7 +181,9 @@ rule init ( version ? : command * : options * : requirement * ) { local machine = [ MATCH "^([^ ]+)" : [ SHELL "$(command-string) -dumpmachine" ] ] ; - version ?= [ dump-version $(command-string) ] ; + if ! $(version) { # ?= operator does not short-circuit + version ?= [ get-short-version $(command-string) ] ; + } switch $(machine:L) { case *mingw* : flavor ?= mingw ; @@ -365,19 +289,45 @@ if [ os.name ] = NT JAMSHELL = % ; } -local rule dump-full-version ( command-string ) +local rule get-version ( command-string ) +{ + local output = [ SHELL $(command-string) : exit-status ] ; + if 0 != $(output[2]) + { + errors.error '$(command-string)' + exited with error code $(output[2]) ; + } + + local version = [ MATCH "^([0-9.]+)" : $(output[1]) ] ; + if ! $(version) + { + errors.error '$(command-string)' + returned an invalid version string '$(output[1])' ; + } + + return $(version) ; +} + +rule get-full-version ( command-string ) { # -dumpfullversion is only supported for gcc 7+. # Passing both options works, as the first one that's # recognized will be used. - return [ MATCH "^([0-9.]+)" : - [ SHELL "$(command-string) -dumpfullversion -dumpversion" ] ] ; + return [ get-version "$(command-string) -dumpfullversion -dumpversion" ] ; } -local rule dump-version ( command-string ) +rule get-short-version ( command-string : single-digit-since ? ) { - return [ MATCH "^([0-9.]+)" : - [ SHELL "$(command-string) -dumpversion" ] ] ; + local version = [ get-version "$(command-string) -dumpversion" ] ; + version = [ SPLIT_BY_CHARACTERS $(version) : . ] ; + + import version ; + if [ version.version-less $(version) : $(single-digit-since:E=5) ] + { + return $(version[1-2]:J=.) ; + } + + return $(version[1]) ; } # Uses -print-prog-name to get the name of the tool. diff --git a/src/util/numbers.jam b/src/util/numbers.jam index d448a6e8f9..bd917b0932 100644 --- a/src/util/numbers.jam +++ b/src/util/numbers.jam @@ -1,3 +1,4 @@ +# Copyright 2021 Nikita Kniazev # Copyright 2001, 2002 Dave Abrahams # Copyright 2002, 2003 Vladimir Prus # Distributed under the Boost Software License, Version 1.0. @@ -61,6 +62,16 @@ rule range ( start finish ? : step ? ) } +rule equal ( n1 n2 ) +{ + if [ CALC $(n2) - $(n1) ] = 0 + { + return true ; + } + return ; +} + + rule less ( n1 n2 ) { switch [ CALC $(n2) - $(n1) ] @@ -150,6 +161,18 @@ rule __test__ ( ) assert.true less 0005 217 ; assert.true less 5 00217 ; + assert.true equal 0 0 ; + assert.true equal 00 0 ; + assert.true equal 0 00 ; + assert.true equal 00 00 ; + assert.true equal 00 000 ; + assert.true equal 123 123 ; + assert.true equal 0123 123 ; + assert.true equal 123 0123 ; + assert.false equal 0 1 ; + assert.false equal 123 124 ; + assert.false equal 124 123 ; + # TEMPORARY disabled, because nested "try"/"catch" do not work and I do no # have the time to fix that right now. if $(0)