Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,93 +13,9 @@
noisyProgress = False


# P1502R1_standard_library_header_units/test.cpp cites the definition of "importable C++ library headers".
def getImportableCxxLibraryHeaders():
return [
'algorithm',
'any',
'array',
'atomic',
'barrier',
'bit',
'bitset',
'charconv',
'chrono',
'codecvt',
'compare',
'complex',
'concepts',
'condition_variable',
'coroutine',
'deque',
'exception',
'execution',
'filesystem',
'format',
'forward_list',
'fstream',
'functional',
'future',
'initializer_list',
'iomanip',
'ios',
'iosfwd',
'iostream',
'istream',
'iterator',
'latch',
'limits',
'list',
'locale',
'map',
'memory',
'memory_resource',
'mutex',
'new',
'numbers',
'numeric',
'optional',
'ostream',
'queue',
'random',
'ranges',
'ratio',
'regex',
'scoped_allocator',
'semaphore',
'set',
'shared_mutex',
'source_location',
'span',
'spanstream',
'sstream',
'stack',
'stdexcept',
'stop_token',
'streambuf',
'string',
'string_view',
'strstream',
'syncstream',
'system_error',
'thread',
'tuple',
'type_traits',
'typeindex',
'typeinfo',
'unordered_map',
'unordered_set',
'utility',
'valarray',
'variant',
'vector',
'version',
]


def loadJsonWithComments(filename):
# This is far from a general-purpose solution (it doesn't attempt to handle block comments like /**/
Copy link
Contributor

Choose a reason for hiding this comment

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

it kinda is a general purpose solution, it's not like a json lexer would do something else ..

Copy link
Member Author

Choose a reason for hiding this comment

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

Well, as the comment explains, a json lexer would know where strings begin and end, so it wouldn't be confused by "cats // dogs" (or more realistically something like "c:/temp/dir1/dir2//file.txt"), whereas my regex here would immediately damage that.

# and comments appearing within strings like "cats // dogs"), but it's sufficient for header-units.json.
# and comments appearing within strings like "cats // dogs"), but it's sufficient for our purposes.
comment = re.compile('//.*')
with open(filename) as file:
replacedLines = [re.sub(comment, '', line) for line in file]
Expand All @@ -111,7 +27,14 @@ def getAllHeaders(headerUnitsJsonFilename):

# We want to build everything that's mentioned in header-units.json, plus all of the
# headers that were commented out for providing macros that control header inclusion.
return sorted(set(buildAsHeaderUnits + ['version', 'yvals.h', 'yvals_core.h']))
return buildAsHeaderUnits + ['version', 'yvals.h', 'yvals_core.h']


def getImportableCxxLibraryHeaders(sourcePath):
# This JSON With Comments file is shared between Python and Perl,
# reducing the number of things we need to update when adding new Standard headers.
jsonFilename = os.path.join(os.path.dirname(sourcePath), 'importable_cxx_library_headers.jsonc')
return loadJsonWithComments(jsonFilename)


class CustomTestFormat(STLTestFormat):
Expand Down Expand Up @@ -188,7 +111,7 @@ def getBuildSteps(self, test, litConfig, shared):
for hdr in readyToBuild:
consumeBuiltHeaderUnits += ['/headerUnit:angle', f'{hdr}={hdr}.ifc']
else: # Build independent header units:
stlHeaders = getImportableCxxLibraryHeaders()
stlHeaders = getImportableCxxLibraryHeaders(sourcePath)
exportHeaderOptions = ['/exportHeader', '/headerName:angle', '/Fo', '/MP']
for hdr in stlHeaders:
consumeBuiltHeaderUnits += ['/headerUnit:angle', f'{hdr}={hdr}.ifc']
Expand Down
245 changes: 153 additions & 92 deletions tests/std/tests/P1502R1_standard_library_header_units/custombuild.pl
Original file line number Diff line number Diff line change
@@ -1,105 +1,166 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

use strict;
use warnings;

use JSON::PP;
use Run;

sub readFile
{
my $filename = $_[0];
open(my $handle, "<", $filename) or die("Couldn't open $filename: $!");
read($handle, my $string, -s $handle);
return $string;
}

sub loadJson
{
my $filename = $_[0];
my $jsonStr = readFile($filename);
return JSON::PP->new->utf8->decode($jsonStr);
Copy link
Contributor

Choose a reason for hiding this comment

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

apparently faster to just write decode_json $jsonStr;

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed. Although that would be shorter and faster, speed isn't important here (these JSON files are very small and the compilation process takes the vast majority of the time), and I felt that having this line look similar to the relaxed line made their near-symmetry more obvious. I suppose I could have just made it a comment.

}

sub loadJsonWithComments
{
my $filename = $_[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

my $filename = shift(@_); is a little more idiomatic, but this is fine too. Personally I prefer just using signatures, but I have no idea if the perl in runall is sufficiently new for that.

Noting for posterity no need to redo the backend integration just for this.

Copy link
Member Author

Choose a reason for hiding this comment

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

For the record, we're running Perl 5.26 (May 2017 🙀) which looks like it would support signatures.

my $jsonStr = readFile($filename);
return JSON::PP->new->relaxed->utf8->decode($jsonStr);
}

sub getAllHeaders
{
my $headerUnitsJsonFilename = $_[0];
my $jsonObject = loadJsonWithComments($headerUnitsJsonFilename);
my @buildAsHeaderUnits = @{$jsonObject->{"BuildAsHeaderUnits"}};
# We want to build everything that's mentioned in header-units.json, plus all of the
# headers that were commented out for providing macros that control header inclusion.
push(@buildAsHeaderUnits, "version", "yvals.h", "yvals_core.h");
return @buildAsHeaderUnits;
}

sub getImportableCxxLibraryHeaders()
{
# This JSON With Comments file is shared between Python and Perl,
# reducing the number of things we need to update when adding new Standard headers.
my $jsonObject = loadJsonWithComments("importable_cxx_library_headers.jsonc");
return @{$jsonObject};
}

sub arrayDifference
{
# (Perl) Takes two arrays by reference. Returns everything in the first array (minuend) that doesn't appear
# in the second array (subtrahend). Doesn't require the arrays to be sorted, so the complexity is O(M * N).
my @minuend = @{$_[0]};
my @subtrahend = @{$_[1]};

my @result = ();
foreach my $elem (@minuend) {
if (!grep($elem eq $_, @subtrahend)) {
push(@result, $elem);
}
}
return @result;
}

sub CustomBuildHook()
{
my $cwd = Run::GetCWDName();

my @stl_headers = (
"algorithm",
"any",
"array",
"atomic",
"barrier",
"bit",
"bitset",
"charconv",
"chrono",
"codecvt",
"compare",
"complex",
"concepts",
"condition_variable",
"coroutine",
"deque",
"exception",
"execution",
"filesystem",
"format",
"forward_list",
"fstream",
"functional",
"future",
"initializer_list",
"iomanip",
"ios",
"iosfwd",
"iostream",
"istream",
"iterator",
"latch",
"limits",
"list",
"locale",
"map",
"memory",
"memory_resource",
"mutex",
"new",
"numbers",
"numeric",
"optional",
"ostream",
"queue",
"random",
"ranges",
"ratio",
"regex",
"scoped_allocator",
"semaphore",
"set",
"shared_mutex",
"source_location",
"span",
"spanstream",
"sstream",
"stack",
"stdexcept",
"stop_token",
"streambuf",
"string",
"string_view",
"strstream",
"syncstream",
"system_error",
"thread",
"tuple",
"type_traits",
"typeindex",
"typeinfo",
"unordered_map",
"unordered_set",
"utility",
"valarray",
"variant",
"vector",
"version",
);

my $export_header_options = "/exportHeader /headerName:angle /Fo /MP";
my $header_unit_options = "";

foreach (@stl_headers) {
$export_header_options .= " $_";

$header_unit_options .= " /headerUnit:angle";
$header_unit_options .= " $_=$_.ifc";
$header_unit_options .= " $_.obj";
# This is a list of compiler options to consume the header units that we've built so far.
my @consumeBuiltHeaderUnits = ();

# Output files:
my @objFilenames = ();

if ($ENV{PM_CL} =~ m</DTEST_TOPO_SORT\b>) { # Build deduplicated header units:
# Compiler options, common to both scanning dependencies and building header units.
my @clOptions = ("/exportHeader", "/headerName:angle", "/translateInclude", "/Fo", "/MP");

# Store the list of headers to build.
my $stlIncludeDir = $ENV{STL_INCLUDE_DIR};
my @allHeaders = getAllHeaders("$stlIncludeDir\\header-units.json");

# Generate JSON files that record how these headers depend on one another.
Run::ExecuteCL(join(" ", @clOptions, "/scanDependencies", ".\\", @allHeaders));

# The JSON files also record what object files will be produced.
# IFC filenames and OBJ filenames follow different patterns. For example:
# <filesystem> produces filesystem.ifc and filesystem.obj
# <xbit_ops.h> produces xbit_ops.h.ifc and xbit_ops.obj
# We can easily synthesize IFC filenames, but it's easier to get the OBJ filenames from the JSON files.

# This dictionary powers the topological sort.
# Key: Header name, e.g. 'bitset'.
# Value: List of dependencies that remain to be built, e.g. ['iosfwd', 'limits', 'xstring'].
my %remainingDependencies;

# Read the JSON files, storing the results in objFilenames and remainingDependencies.
foreach my $hdr (@allHeaders) {
my $jsonObject = loadJson("$hdr.module.json");
push(@objFilenames, $jsonObject->{"rules"}[0]{"primary-output"});

my @dep = ();
foreach my $req (@{$jsonObject->{"rules"}[0]{"requires"}}) {
push(@dep, $req->{"logical-name"});
}
$remainingDependencies{$hdr} = \@dep;
}

# Build header units in topologically sorted order.
while (%remainingDependencies) {
# When a header has no remaining dependencies, it is ready to be built.
my @readyToBuild = ();
foreach my $hdr (keys(%remainingDependencies)) {
my @dep = @{$remainingDependencies{$hdr}};
if (!@dep) {
push(@readyToBuild, $hdr);
}
}

# Each layer can be built in parallel.
Run::ExecuteCL(join(" ", @clOptions, @consumeBuiltHeaderUnits, @readyToBuild));

# Update remainingDependencies by doing two things.

# (Perl) First, eliminate headers that we just built.
foreach my $hdr (@readyToBuild) {
delete $remainingDependencies{$hdr};
}

# hdr, dep is the current key-value pair.
foreach my $hdr (keys(%remainingDependencies)) {
my @dep = @{$remainingDependencies{$hdr}};

# Second, filter dep, eliminating anything that appears in readyToBuild. (If we're left with
# an empty list, then hdr will be ready to build in the next iteration.)
my @filtered = arrayDifference(\@dep, \@readyToBuild);

$remainingDependencies{$hdr} = \@filtered;
}

# Add compiler options to consume the header units that we just built.
foreach my $hdr (@readyToBuild) {
push(@consumeBuiltHeaderUnits, "/headerUnit:angle", "$hdr=$hdr.ifc");
}
}
} else { # Build independent header units:
my @stlHeaders = getImportableCxxLibraryHeaders();
my @exportHeaderOptions = ("/exportHeader", "/headerName:angle", "/Fo", "/MP");

foreach my $hdr (@stlHeaders) {
push(@consumeBuiltHeaderUnits, "/headerUnit:angle", "$hdr=$hdr.ifc");
push(@objFilenames, "$hdr.obj");
}

Run::ExecuteCL(join(" ", @exportHeaderOptions, @stlHeaders));
}

Run::ExecuteCL("$export_header_options");
Run::ExecuteCL("test.cpp /Fe$cwd.exe $header_unit_options");
# For convenience, create a library file containing all of the object files that were produced.
my $libFilename = "stl_header_units.lib";
Run::ExecuteCommand(join(" ", "lib.exe", "/nologo", "/out:$libFilename", @objFilenames));

Run::ExecuteCL(join(" ", "test.cpp", "/Fe$cwd.exe", @consumeBuiltHeaderUnits, $libFilename));
}
1
Loading