Hi Bill
Given Parts makes components I needed a way to share data. I tried the Export
Import logic, but had issues with it. In the end I used Import to pass in the
env and other functions I add in Parts.
This is why all .parts files start with Import(‘*’). I could get around this my
taking more control, but this seemed to be counter productive to my hope of
getting Parts logic into SCons.
So to pass data around and to request what data is needed we have a DependsOn()
function ( and a Exportxxx()/SDKxxx functions that push values to be sharable”
that allows the user define they depend on a component with certain requires,
like version or other features ( given more than on Part is defined in the
SConstruct as it common if you cross build in a single pass). I this call you
can define some values that you require from that component via the REQ object.
Certain values are pass automatically such as CPPPATH, and LIBS.. ie values you
need to compile against the component, while other values need to be explicitly
requested, such as CPPFLAGS or custom values that may have been export for the
Component. When you DependOn([Component(A)) Parts will add some strings to map
implicated values. These may not get expanded at all and at worse waste some
space in the environment. Explicted value defined by the REQ meta object also
get mapped. For this e-maili will keep it simple and leave out some features.
The main point is that the string we add is something like
“{PARTIDEXPORTS(‘sub1@version:1.*’,’build’,’LIBS’,’1’)}”
This call a parts.mapper object ( we have a few defined in Parts). This object
will look up the needed object and get the requested data in the dependent
component and add it to the environment variable in question. This subst
happens when SCons subst the code. We do some tricks to avoid extra look ups
and to replace values that are known. This way Part A can depend on Part B and
C and we can add to the LIBS and LIBSPATH variable the needed information
without the developer bothering to do this. What is also nice about this is
that if B was making a Boo.lib files and it changes to a BOB1.2.lib file, the
only change needed is the in B.part files. The change will auto propagate
correctly up the depends chain and everything will just rebuild. As I stated
this happens via calling an python object in the subst() call and having the
code correctly return a value and or a value and update in place a value in the
a list ( as is common with CPPFLAGS, LIBS, etc..) In the end you do a build (
and you can look at samples in Parts to see this happening) when the part file
is done being read we might see something like:
Given a setup such as
a->[B,C]
B->[]
C->[D]
Env[‘LIBS’] -> [‘a.lib’,’ {PARTIDEXPORTS(‘B@version:2.*’,’build’,’LIBS’,’1’)}’,
{PARTIDEXPORTS(‘C@version:1.*’,’build’,’LIBS’,’1’)}”]
But when the subst for like “LINKCOM” happens it will get for the $LIBS a list
like -> [‘a.lib’,’b.lib’,’c.lib’, ’d.lib’]. technically this happens with the
Scanner first for items LIBS, LIBPATH, CPPPATH, so when the command subst
happens we already replaced ourselves to prevent redundant logic from happening.
If you look at the history this was simple at first, but then Gary complained I
made it to C\C++ specific so it could never get into SCons, I tweaked to make
it more general, then I needed to make it better to allow custom values. In
this end other tweaks have been made. The issue is that this code goes off a
lot and takes time to process in Large builds. Work has been done to replace
the SCons version of _concat_ixes(), and some other objects to make them not
faster. The last major update we had made was to try to fix an issue in which
we had a Vtune with some messed up depends circular depends in which the mapper
code would flip an include path around with mid-level dependent components,
cause a rebuild because SCons saw a change in the environment. This code has
been a sore spot for me to a degree as it never quite worked right in certain
complex cases was done to delay subst values because we would not know at load
time which variant of Part C you needed to get value from until we loaded all
the components. The current form is a bit complex and ugly honestly. There are
some odd work around for some odd behavior with CmdStringHolder class.
Given what I have learned…. The plan is to replace this given I get time to do
this via implementing:
1. A new Parts format using @decorators to delay process functions ( ie make
the Parts file loading not an exec off a lot of python code, but a register
callback) This will allow me to load all the “parts” and have needed
information such as name, version and other platform information before I try
to process what the component will emit as build actions.
2. For the existing format implement a “continuous loader”.. or load a
Parts file until I get to a DependsOn statement. At the point I pause and start
loading another Part until all the primary “top” level Parts are loaded. At
that point I know enough information to know what is at the bottom and what is
at the top…
In both of these cases this allows me to delay “processing” the expensive
stuff, such as env.Program([list of 1000 files]) until I really need to because
I know it is the build “target” list. I also can on first pass ( ie no DB) can
load the Parts in order and insert the needed values directly. Ie add “B.lib”
or “b/includes” to the needed item. I don’t need to subst and code. At most I
might need to move some values around, such as libs to make sure the linker is
happy and we avoid double ( or more) inserts of the same values. This would
allow Parts to start SCons up much faster as we can:
1. Avoid loading all the components if the target is to not build “all” or
“.”
2. Avoid a lot of subst() calls during the build allowing SCons, while
allowing me a nice way to share data in an organized way. This will also allow
me to share objects between components vs only strings. This will be good as it
will allow a “part” to define a builder and export it to be shared by other
components. Which is a more common request than I though. I have ugly work
around at the moment to do this today.. but I rather not speak about them 😊
3. Independent of mappers, this allows for other speed ups such as possible
avoiding of code execution such as env.Builder([lots of objects]) what would
not be called or better yet avoid calls to SCons Glob() or the Parts Pattern()
which scan the disk for files. This was found in profiling to be one of the
biggest reasons why it took time to load a part. We had cases such as boost in
which the a few scans happened ( one for headers to export and few different
ones for sources to build different boost components) which would resulted in
the file taking 30 second to load not average .02 – .5 seconds on a laptop
spinning disk the other parts generally took that did not scan large number of
files. ( this is one reason for the use cache better argument I made before, as
we should only need to scan a directory at most once, and then only again on a
change. I had prototype this in Parts once and it showed a big improvement in
load time for the read phase.. never was finished it ☹, but for me the point is
that once we know what it is we should save it and not reproduce calculating it
until something says we need too. File and directory nodes, and subst string
values I had used to pass libs, paths and other values between components are
great examples of this )
I hope this helps… let me know if you have other questions.
You can also install Parts with SCons on a box and run a some of my samples..
add a --trace=mappers should give you a big dump of data. ( ie add --verbose
and --trace will dump everything… adding the [category] printed with a verbose
or trace message to the –trace or –verbose separated with commas will allow you
to filter to a smaller set of data. It will help show what is going one and how
Parts shares data
Jason
In case you want a simple example of what this might look like without playing
with code
# hello part depends on print_msg parts
PartName('hello')
env.DependsOn([Component('print_msg','1.*')])
ret=env.Program("hello","hello.c")
env.InstallTarget(ret)
------------
# the print_msg.part
PartVersion('1.0.0')
PartName('print_msg')
#files
cpp_files=['print_msg.c']
…
outputs=env.SharedLibrary('print_msg',cpp_files)
#This will auto export the print_msg.lib file as well other stuff related to
install sandbox and possible package creation
env.InstallTarget(outputs)
# this exports the print_msg.h include path
env.SdkInclude(['print_msg.h'])
---
#SConstruct
from parts import *
Part('hello/hello.parts')
Part('print_msg/print_msg.parts')
From: Bill Deegan [mailto:[email protected]]
Sent: Monday, July 24, 2017 5:40 PM
To: SCons developer list <[email protected]>; Jason Kenny <[email protected]>
Subject: Variable Substitution
Jason,
You mentioned:
Variable Substitution:
I abuse this in Parts to share data in a lazy fashion between components. It
has been a sore point for me, given reason stated below. We have done some work
to address the items by reusing states better. I can say there are some issues
with the current code that causes memory bloat and wasted time. I don’t want to
dwell on this, but will say that this is the second biggest item in my mind
that would have a big impact to overall time to the user. I know I want to
change the load logic in Parts to avoid using the substitution engine as much
as possible.
Can you expand upon how you abuse variable substitution?
Is it about storing information for later use that will never really be
expanded by Subst()?
Or something else?
Thanks,
Bill
_______________________________________________
Scons-dev mailing list
[email protected]
https://pairlist2.pair.net/mailman/listinfo/scons-dev