Boost logo

Boost-Build :

From: Chambers, Matthew (matt.chambers42_at_[hidden])
Date: 2020-06-24 17:16:32


I have hit an intermittent (hard to reproduce) issue when building a particular target with MSVC. I've added custom versions of msvc.link
and msvc.compile for /clr and .NET support. They've been working fine for the few targets I originally developed them for. This problem
started happening when I recently added another target using these custom versions. The symptom is that I get this link command with doubled
up "link" and flags suggesting the $(.LD) and $(LINKFLAGS) variables are getting doubled up. It's so hard to reproduce I haven't managed to
get any --debug-configuration output when it happens. I've only gotten it to happen on our AWS build agents. I was hoping one of the
veterans would have a hunch about what's happening just from the symptom.

 Â link link /NOLOGO /NOLOGO /INCREMENTAL:NO /INCREMENTAL:NO /DLL /DEBUG /DEBUG /MACHINE:X64 /MACHINE:X64 /MANIFEST /MANIFEST
/subsystem:console /subsystem:console /out:"Z:\path\to\PrmPasefScheduler.dll" /LIBPATH:"Z:\some\path\x64" /LIBPATH:"Z:\some\other\path\x64" 
/MACHINE:x64 /FIXED:No /MACHINE:x64 /FIXED:No @"Z:\path\to\PrmPasefScheduler.dll.rsp"

Just in case you want to dig deeper, here is the problematic target:

rule msvc-requirement ( properties * )
{
 Â Â Â  if ! <toolset>msvc in $(properties) { return <build>no ; }
}
obj PrmScheduler       : PrmScheduler.cpp        : <conditional>@msvc-requirement <define>UNICODE <define>_UNICODE
<toolset>msvc:<using-clr>true ;

rule prmscheduler-requirements ( properties * )
{
 Â Â Â  if <address-model>64 in $(properties)
 Â Â Â  {
 Â Â Â Â Â Â Â  return <search>x64 ;
 Â Â Â  }
 Â Â Â  else
 Â Â Â  {
 Â Â Â Â Â Â Â  return <search>x86 ;
 Â Â Â  }
}

rule prmscheduler-usage-requirements ( properties * )
{
 Â Â Â  if <address-model>64 in $(properties)
 Â Â Â  {
 Â Â Â Â Â Â Â  return <assembly-dependency>$(PRMSCHEDULER_CLI_ROOT)/x64/prmscheduler.dll ;
 Â Â Â  }
 Â Â Â  else
 Â Â Â  {
 Â Â Â Â Â Â Â  return <assembly-dependency>$(PRMSCHEDULER_CLI_ROOT)/x86/prmscheduler.dll ;
 Â Â Â  }
}

searched-lib prmscheduler : : <conditional>@prmscheduler-requirements : : <conditional>@prmscheduler-usage-requirements ;

constant PLATFORM : "x64" ;
lib PrmPasefScheduler
 Â Â Â  : # sources
 Â Â Â Â Â Â Â  PrmScheduler
 Â Â Â  : # requirements
 Â Â Â Â Â Â Â  <library>prmscheduler
 Â Â Â Â Â Â Â  <link>shared
<library>$(PWIZ_ROOT_PATH)/pwiz/utility/misc//pwiz_utility_misc/<link>static/<using-clr>false
 Â Â Â Â Â Â Â  <linkflags>"/MACHINE:$(PLATFORM) /FIXED:No"
 Â Â Â Â Â Â Â  <cxxflags>/permissive
 Â Â Â Â Â Â Â  <conditional>@msvc-requirement
 Â Â Â Â Â Â  <define>UNICODE
 Â Â Â Â Â Â  <define>_UNICODE
 Â Â Â Â Â Â  <toolset>msvc:<using-clr>true
 Â Â Â  : # default-build
 Â Â Â  : # usage-requirements
 Â Â Â Â Â Â Â  <library>prmscheduler
<library>$(PWIZ_ROOT_PATH)/pwiz/utility/misc//pwiz_utility_misc/<link>static/<using-clr>false
 Â Â Â  ;

_
__Here are the custom rules/actions:_

actions compile-c++clr
{
 Â Â Â  $(.SETUP) $(.CC) @"@($(<[1]:W).rsp:E="$(>[1]:W)" -Fo"$(<[1]:W)" -Yu"$(>[3]:D=)" -Fp"$(>[2]:W)" /clr $(ASSEMBLY_INCLUDE_PATHS)
$(ASSEMBLIES) $(CC_RSPLINE))" $(.CC.FILTER)" > "$(<[1]:W).out"
 Â Â Â  set status=%ERRORLEVEL%
 Â Â Â  findstr /V /X "$(>[1]:D=)" "$(<[1]:W).out"
 Â Â Â  exit %status%
}

rule compile.c++clr ( targets + : sources * : properties * )
{
 Â Â Â  set-setup-command $(targets) : $(properties) ;
 Â Â Â  set-assemblies $(<[1]) : $(properties) ;
 Â Â Â  get-rspline $(targets) : -TP ;
 Â Â Â  compile-c++clr $(<) : $(>) [ on $(<) return $(PCH_FILE) ] [ on $(<) return $(PCH_HEADER) ] ;
}

actions link.clr.dll bind DEF_FILE LIBRARIES_MENTIONED_BY_FILE
{
 Â Â Â  $(.SETUP) $(.LD) /DLL $(LINKFLAGS) /out:"$(<[1]:W)" /LIBPATH:"$(LINKPATH:W)" /def:"$(DEF_FILE)" $(OPTIONS)
@"@($(<[1]:W).rsp:E=$(.nl)"$(>)" $(.nl)$(LIBRARIES_MENTIONED_BY_FILE) $(.nl)$(LIBRARIES) $(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_ST).lib"
$(.nl)"$(LIBRARY_OPTION)$(FINDLIBS_SA).lib")" $(MANIFEST_DEPENDENCIES)
 Â Â Â  if %ERRORLEVEL% NEQ 0 EXIT %ERRORLEVEL%
}

rule link.clr.dll ( targets + : sources * : properties * )
{
 Â Â Â  set-setup-command $(targets) : $(properties) ;
 Â Â Â  set-manifest-dependencies $(<[1]) : $(properties) ;
 Â Â Â  DEPENDS $(<) : [ on $(<) return $(DEF_FILE) ] ;
 Â Â Â  if <embed-manifest>on in $(properties)
 Â Â Â  {
 Â Â Â Â Â Â Â  msvc.manifest.dll $(targets) : $(sources) : $(properties) ;
 Â Â Â  }
 Â Â Â  copy-assemblies $(<[1]) : $(properties) ;
}

_
__Here are the custom features:_

feature.feature assembly            : : free dependency ;
feature.feature assembly-dependency : : free dependency ;
feature.feature assembly-dependency-ex : : free ;
feature.feature manifest-dependency : : free dependency ;
feature.feature configuration-file  : : free dependency ;
feature.feature using-clr           : false true : composite ;
feature.compose <using-clr>true     : <asynch-exceptions>on <runtime-link>shared ;

generators.register [ new msvc-linking-generator msvc.link.clr.dll : OBJ SEARCHED_LIB STATIC_LIB : SHARED_LIB : <toolset>msvc
<using-clr>true ] ;
generators.override msvc.link.clr.dll : msvc.link.dll ;

generators.register-c-compiler msvc.compile.c++clr : CPP : OBJ : <toolset>msvc <using-clr>true ;

_
__Here are some utility functions used in the above actions:_

#
# Add /FU and /AI flags for targets using /clr
#
rule set-assemblies ( target : properties * )
{
 Â Â Â  local assemblies-string ;
 Â Â Â  local assemblies = [ feature.get-values <assembly> : $(properties) ] ;
 Â Â Â  if $(assemblies)
 Â Â Â  {
 Â Â Â Â Â Â Â  for local assembly in $(assemblies)
 Â Â Â Â Â Â Â  {
 Â Â Â Â Â Â Â Â Â Â Â  local assembly-path = [ $(assembly).name ] ;
 Â Â Â Â Â Â Â Â Â Â Â  if $(assembly-path:S) != ".pdb"
 Â Â Â Â Â Â Â Â Â Â Â  {
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  if ! [ path.is-rooted $(assembly-path) ]
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  {
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  assembly-path = [ path.join [ $(assembly).path ] $(assembly-path:D=) ] ;
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  }
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  assembly-path = [ path.native $(assembly-path) ] ;
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  assemblies-string += " /FU\"$(assembly-path)\"" ;
 Â Â Â Â Â Â Â Â Â Â Â  }
 Â Â Â Â Â Â Â  }
 Â Â Â  }
 Â Â Â  ASSEMBLIES on $(target) = $(assemblies-string) ;

 Â Â Â  # add /AI flags for each assembly-dependency's (unique) directory
 Â Â Â  local assembly-include-string ;
 Â Â Â  local assembly-dependencies = [ feature.get-values <assembly-dependency> : $(properties) ] ;
 Â Â Â  if $(assembly-dependencies)
 Â Â Â  {
 Â Â Â Â Â Â Â  local assembly-include-paths ;
 Â Â Â Â Â Â Â  for local dependency in $(assembly-dependencies)
 Â Â Â Â Â Â Â  {
 Â Â Â Â Â Â Â Â Â Â Â  local dependency-path = [ $(dependency).name ] ;
 Â Â Â Â Â Â Â Â Â Â Â  if ! [ path.is-rooted $(dependency-path) ]
 Â Â Â Â Â Â Â Â Â Â Â  {
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  dependency-path = [ path.join [ $(dependency).path ] $(dependency-path:D=) ] ;
 Â Â Â Â Â Â Â Â Â Â Â  }
 Â Â Â Â Â Â Â Â Â Â Â  dependency-path = [ path.native $(dependency-path) ] ;
 Â Â Â Â Â Â Â Â Â Â Â  dependency-path = $(dependency-path:D) ;
 Â Â Â Â Â Â Â Â Â Â Â  if ! $(dependency-path) in $(assembly-include-paths)
 Â Â Â Â Â Â Â Â Â Â Â  {
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  assembly-include-paths += $(dependency-path) ;
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  assembly-include-string += " /AI\"$(dependency-path)\"" ;
 Â Â Â Â Â Â Â Â Â Â Â  }
 Â Â Â Â Â Â Â  }
 Â Â Â  }
 Â Â Â  ASSEMBLY_INCLUDE_PATHS on $(target) = $(assembly-include-string) ;
}

#
# Add /MANIFESTDEPENDENCY flags (primarily for side-by-side loading of COM DLLs)
#
local rule set-manifest-dependencies ( target : properties * )
{
 Â Â Â  local manifest-dependencies-string ;
 Â Â Â  local manifest-dependencies = [ feature.get-values <manifest-dependency> : $(properties) ] ;
 Â Â Â  if $(manifest-dependencies)
 Â Â Â  {
 Â Â Â Â Â Â Â  # type='win32' name='<assembly_basename>' version='<assembly_version>'
 Â Â Â Â Â Â Â  for local dependency in $(manifest-dependencies)
 Â Â Â Â Â Â Â  {
 Â Â Â Â Â Â Â Â Â Â Â  local dependency-path = [ path.native [ $(dependency).name ] ] ;
 Â Â Â Â Â Â Â Â Â Â Â  local dependency-version = [ MATCH "FileVersion:     ([0-9\\.]+)" : [ SHELL "ShowVer.exe $(dependency-path)" ] ] ;

 Â Â Â Â Â Â Â Â Â Â Â  # unknown version will cause a runtime-error; is there a better solution?
 Â Â Â Â Â Â Â Â Â Â Â  dependency-version ?= "unknown" ;

 Â Â Â Â Â Â Â Â Â Â Â  # the name of the manifest can not match the basename of the DLL because of a Windows XP loader bug
 Â Â Â Â Â Â Â Â Â Â Â  manifest-dependencies-string += " /MANIFESTDEPENDENCY:\"type='win32' name='$(dependency-path:B).SxS'
version='$(dependency-version)'\"" ;
 Â Â Â Â Â Â Â  }
 Â Â Â  }
 Â Â Â  MANIFEST_DEPENDENCIES on $(target) = $(manifest-dependencies-string) ;
}

#
# Copy .NET assemblies and their native DLL dependencies to the target's directory;
# Also .NET applications may need a side-by-side configuration file that must be named like $(target).exe.config - copy that if necessary
#
rule copy-assemblies ( target : properties * )
{
 Â Â Â  local assemblies = [ feature.get-values <assembly> : $(properties) ] ;
 Â Â Â  assemblies += [ feature.get-values <assembly-dependency> : $(properties) ] ;
 Â Â Â  if $(assemblies)
 Â Â Â  {
 Â Â Â Â Â Â Â  local target-path = [ on $(target) return $(LOCATE) ] ;
 Â Â Â Â Â Â Â  for local assembly in $(assemblies)
 Â Â Â Â Â Â Â  {
 Â Â Â Â Â Â Â Â Â Â Â  local assembly-path = [ $(assembly).name ] ;
 Â Â Â Â Â Â Â Â Â Â Â  if ! [ path.is-rooted $(assembly-path) ]
 Â Â Â Â Â Â Â Â Â Â Â  {
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  assembly-path = [ path.join [ $(assembly).path ] $(assembly-path:D=) ] ;
 Â Â Â Â Â Â Â Â Â Â Â  }
 Â Â Â Â Â Â Â Â Â Â Â  assembly-path = [ path.native $(assembly-path) ] ;
 Â Â Â Â Â Â Â Â Â Â Â  local assembly-at-target-filepath = [ path.native [ path.join $(target-path) $(assembly-path:D=) ] ] ;

 Â Â Â Â Â Â Â Â Â Â Â  if $(assembly-at-target-filepath:L) != $(assembly-path:L) &&
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â  ! ( $(assembly-at-target-filepath:L) in $(.unique-targets) )
 Â Â Â Â Â Â Â Â Â Â Â  {
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  .unique-targets += $(assembly-at-target-filepath:L) ;
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  if ! [ CHECK_IF_FILE $(assembly-at-target-filepath:L) ]
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  {
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  common.hard-link $(assembly-at-target-filepath) : $(assembly-path) ;
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  DEPENDS $(assembly-at-target-filepath) : $(target-path) [ $(assembly).actualize ] ;
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  DEPENDS $(target) : $(assembly-at-target-filepath) ;
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  #JAM_SEMAPHORE on $(target-filepath) = "dotNetSemaphore" ;
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  #JAM_SEMAPHORE on $(target) = "dotNetSemaphore" ;
 Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â  }
 Â Â Â Â Â Â Â Â Â Â Â  }
 Â Â Â Â Â Â Â  }
 Â Â Â  }

 Â Â Â  # .NET applications may need a side-by-side configuration file that must be named like $(target).exe.config
 Â Â Â  local configuration-file = [ feature.get-values <configuration-file> : $(properties) ] ;
 Â Â Â  if $(configuration-file)
 Â Â Â  {
 Â Â Â Â Â Â Â  local target-path = [ on $(target) return $(LOCATE) ] ;
 Â Â Â Â Â Â Â  local configuration-file-path = [ $(configuration-file).name ] ;
 Â Â Â Â Â Â Â  if ! [ path.is-rooted $(configuration-file-path) ]
 Â Â Â Â Â Â Â  {
 Â Â Â Â Â Â Â Â Â Â Â  configuration-file-path = [ path.join [ $(configuration-file).path ] $(configuration-file-path:D=) ] ;
 Â Â Â Â Â Â Â  }
 Â Â Â Â Â Â Â  configuration-file-path = [ path.native $(configuration-file-path) ] ;
 Â Â Â Â Â Â Â  local target-filepath = [ path.native [ path.join $(target-path) $(configuration-file-path:D=) ] ] ;
 Â Â Â Â Â Â Â  if $(target-filepath) != $(configuration-file-path) &&
 Â Â Â Â Â Â Â Â Â Â  ! ( $(configuration-file-path) in $(.unique-targets) )
 Â Â Â Â Â Â Â  {
 Â Â Â Â Â Â Â Â Â Â Â  .unique-targets += $(configuration-file-path) ;
 Â Â Â Â Â Â Â Â Â Â Â  common.hard-link $(target-filepath) : $(configuration-file-path) ;
 Â Â Â Â Â Â Â Â Â Â Â  DEPENDS $(target-filepath) : $(target-path) ;
 Â Â Â Â Â Â Â Â Â Â Â  DEPENDS $(target) : $(target-filepath) ;
 Â Â Â Â Â Â Â Â Â Â Â  #JAM_SEMAPHORE on $(target-filepath) = "dotNetSemaphore" ;
 Â Â Â Â Â Â Â Â Â Â Â  #JAM_SEMAPHORE on $(target) = "dotNetSemaphore" ;
 Â Â Â Â Â Â Â  }
 Â Â Â  }
}



Boost-Build list run by bdawes at acm.org, david.abrahams at rcn.com, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk