Repository: mattgodbolt/compiler-explorer (js)

Issue:
Restrict library versions to be visible only when using specific compilers
**Is your feature request related to a problem? Please describe.**
I'm setting up a local compiler explorer instance for my company. We have several product versions, each of which ships with a compiler capable of targetting several architectures, and each of those architectures has their own set of static libraries available to them.

I am trying to create a workflow where a user can select compilers targetting different architectures from the compiler dropdown menu, and depending on which architecture their compiler is specified as targetting, different static library paths will be chosen for libraries they choose to add. My rationale for this is that it would be helpful to have an accurate representation of what the generated code looks like when compiling with the "Compile to Binary" option enabled, without making the user have to select their target architecture more than once.

**Describe the solution you'd like**
In a perfect world I'd like to have a configuration option exposed to limit the libraries available for a given compiler to a subset of the libraries defined under `libs`.

For instance, if a library `widget` existed with library versions `trunk-x86_64` and `trunk-aarch64` defined, a compiler `gcc_x86_64` could be restricted to only have the x86_64 version of the library visible through `compiler.gcc_x86_64.libs=widget.trunk-x86_64`.

**Describe alternatives you've considered**
Issue #1590 mentions that "we already [have] libraries per compiler", and it seems like some mechanism is in place to allow external compiler explorer instances to integrate their own compilers with unique library versions together (as seems to be the case with godbolt.ms from godbolt.org), but using a model like that in this case seems like it would lead to a full compiler explorer instance running per product version, per architecture, which in our case could mean hundreds of servers running. That seems very inefficient way to filter a list of file path strings. I admit that I haven't been able to figure out how that integration works yet by looking at the code, so I might be missing some straightforward solution to this.

One solution that comes to mind is making library versions for all architectures visible in the library version selection list, and relying on the user to choose the right library for their architecture of choice. This seems error-prone, since for every library a user might want to include, they would have to select the right choice from a (potentially quite long) list of similar-looking architectures, and possibly silently causing the wrong behaviour if they choose the wrong one.

Another solution I've considered is bumping up the level of abstraction, giving each product version its own compiler explorer instance and making each language in the instance qualified by the architecture its compilers will be targetting, which would allow the desired behaviour to exist (since libraries can be defined per-language). it might work t, but it doesn't seem like a good idea, since it's using compiler explorer features in ways that aren't really related to how they're supposed to work, and in this case it would make the compiler selection dropdown basically useless since there would only ever be one item available.

**Additional context**
If anyone has any suggestions about existing ways to solve this problem, I'm more than happy to hear them. I might be totally missing something in the CE codebase that supports this use case!

Pull request description:
Limit libraries per compiler
Should close #1949

Initially, I mentioned a prop on the compiler would probably a bad thing, but I realized that if a compiler doesn't support any libraries, you could say 'supportsLibraries=none'. Assuming no library is called none that is!


Patch:
diff --git a/etc/config/c++.amazon.properties b/etc/config/c++.amazon.properties
index d672afe73..2c6c071bc 100644
--- a/etc/config/c++.amazon.properties
+++ b/etc/config/c++.amazon.properties
@@ -96,6 +96,7 @@ compiler.g93.semver=9.3
 compiler.gsnapshot.exe=/opt/compiler-explorer/gcc-snapshot/bin/g++
 compiler.gsnapshot.semver=(trunk)
 compiler.gsnapshot.alias=g7snapshot
+compiler.gsnapshot.demangler=/opt/compiler-explorer/gcc-snapshot/bin/c++filt
 compiler.gcontracts-trunk.exe=/opt/compiler-explorer/gcc-lock3-contracts-trunk/bin/g++
 compiler.gcontracts-trunk.semver=(contracts)
 compiler.gcontracts-trunk.notification=Experimental Contract Assertions; see <a href="https://gitlab.com/lock3/gcc-new/wikis/contract-assertions" target="_blank" rel="noopener noreferrer">Lock3's repository wiki<sup><small class="fas fa-external-link-alt opens-new-window" title="Opens in a new window"></small></sup></a>
@@ -205,6 +206,7 @@ compiler.clang1000.options=--gcc-toolchain=/opt/compiler-explorer/gcc-9.3.0
 compiler.clang_trunk.exe=/opt/compiler-explorer/clang-trunk/bin/clang++
 compiler.clang_trunk.semver=(trunk)
 compiler.clang_trunk.options=--gcc-toolchain=/opt/compiler-explorer/gcc-9.2.0
+compiler.clang_trunk.demangler=/opt/compiler-explorer/gcc-snapshot/bin/c++filt
 compiler.clang_concepts.exe=/opt/compiler-explorer/clang-concepts-trunk/bin/clang++
 compiler.clang_concepts.semver=(old concepts branch)
 compiler.clang_concepts.options=-std=c++2a -Xclang -fconcepts-ts -stdlib=libc++
@@ -689,7 +691,7 @@ compiler.djggp494.semver=4.9.4
 # Installed libs
 libs=boost:brigand:kvasir:cmcstl2:mp-units:ctbignum:gsl:expected_lite:nlohmann_json:tomlplusplus:xtl:xsimd:xtensor:abseil:blaze:ctre:eigen:benchmark:rangesv3:dlib:libguarded:cppcoro:fmt:hfsm:glm:llvm:catch2:doctest:eastl:vcl:outcome:cnl:googletest:tbb:seastar:pegtl:openssl:nanorange:benri:hedley:gnufs:llvmfs:etl:namedtype:spy
 libs.boost.name=Boost
-libs.boost.versions=164:165:166:167:168:169:170:171:172
+libs.boost.versions=164:165:166:167:168:169:170:171:172:173
 libs.boost.url=https://www.boost.org
 libs.boost.versions.164.version=1.64.0
 libs.boost.versions.164.path=/celibs/boost_1_64_0
@@ -709,6 +711,8 @@ libs.boost.versions.171.version=1.71.0
 libs.boost.versions.171.path=/celibs/boost_1_71_0
 libs.boost.versions.172.version=1.72.0
 libs.boost.versions.172.path=/celibs/boost_1_72_0
+libs.boost.versions.173.version=1.73.0
+libs.boost.versions.173.path=/celibs/boost_1_73_0
 libs.brigand.name=Brigand
 libs.brigand.versions=trunk:130
 libs.brigand.url=https://github.com/edouarda/brigand
diff --git a/etc/config/rust.amazon.properties b/etc/config/rust.amazon.properties
index f4e9ce840..1b667f900 100644
--- a/etc/config/rust.amazon.properties
+++ b/etc/config/rust.amazon.properties
@@ -1,11 +1,13 @@
 compilers=&rust
-objdumper=/opt/compiler-explorer/gcc-9.1.0/bin/objdump
-defaultCompiler=r1420
+objdumper=/opt/compiler-explorer/gcc-9.3.0/bin/objdump
+defaultCompiler=r1430
 demangler=/opt/compiler-explorer/demanglers/rust/bin/rustfilt
-group.rust.compilers=r1420:r1410:r1400:r1390:r1380:r1370:r1360:r1350:r1340:r1330:r1320:r1310:r1300:r1290:r1280:r1271:r1270:r1260:r1250:r1240:r1230:r1220:r1210:r1200:r1190:r1180:r1170:r1160:r1151:r1140:r1130:r1120:r1110:r1100:r190:r180:r170:r160:r150:r140:r130:r120:r110:r100:nightly:beta
+group.rust.compilers=r1430:r1420:r1410:r1400:r1390:r1380:r1370:r1360:r1350:r1340:r1330:r1320:r1310:r1300:r1290:r1280:r1271:r1270:r1260:r1250:r1240:r1230:r1220:r1210:r1200:r1190:r1180:r1170:r1160:r1151:r1140:r1130:r1120:r1110:r1100:r190:r180:r170:r160:r150:r140:r130:r120:r110:r100:nightly:beta
 group.rust.compilerType=rust
 group.rust.isSemVer=true
 group.rust.baseName=rustc
+compiler.r1430.exe=/opt/compiler-explorer/rust-1.43.0/bin/rustc
+compiler.r1430.semver=1.43.0
 compiler.r1420.exe=/opt/compiler-explorer/rust-1.42.0/bin/rustc
 compiler.r1420.semver=1.42.0
 compiler.r1410.exe=/opt/compiler-explorer/rust-1.41.0/bin/rustc
diff --git a/etc/config/sponsors.yaml b/etc/config/sponsors.yaml
index 76944fab3..df0c5100d 100644
--- a/etc/config/sponsors.yaml
+++ b/etc/config/sponsors.yaml
@@ -12,7 +12,6 @@ levels:
         url: https://stefan-hagen.website/
       - name: Robert Douglas
         url: https://www.youtube.com/channel/UCxwGBrWeIIWO9V_uLwRWCdA/videos
-      - Dylan Houlihan
       - Igor Zarzycki
       - TocarIP
       - name: Jane Lusby
@@ -21,6 +20,10 @@ levels:
       - name: Ondra Karlik
         url: https://cgg.mff.cuni.cz/~jaroslav/
         description: In memory of Jaroslav Křivánek
+      - name: Matthew Guidry
+        url: https://blog.matthewguidry.dev/
+      # Person with the initials "WB" prefers to remain anonymous
+      - name: Casey Carter
   - name: Patreons
     description: 'Thanks to all my patrons:'
     sponsors:
@@ -169,7 +172,6 @@ levels:
       - Alejandro Lucena
       - xyx
       - Chris Luke
-      - Casey Carter
       - Adam Greig
       - Eric Perko
       - Christopher Di Bella
@@ -218,3 +220,10 @@ levels:
       - Jon Raymond
       - Bene
       - Billy
+      - Stefan Wüthrich
+      - Yin Zhong
+      - Dan Raviv
+      - Yacob Cohen-Arazi
+      - Tobias Ribizel
+      - Joel Falcou
+      - Dylan Houlihan
diff --git a/lib/compiler-finder.js b/lib/compiler-finder.js
index dd0af8ccd..19977cfbc 100644
--- a/lib/compiler-finder.js
+++ b/lib/compiler-finder.js
@@ -146,6 +146,50 @@ class CompilerFinder {
         }));
     }
 
+    copyAndFilterLibraries(allLibraries, filter) {
+        const filterLibAndVersion = _.map(filter, (lib) => {
+            const match = lib.match(/([a-z0-9_-]*)\.([a-z0-9_-]*)/i);
+            if (match) {
+                return {
+                    id: match[1],
+                    version: match[2]
+                };
+            } else {
+                return {
+                    id: lib,
+                    version: false
+                };
+            }
+        });
+
+        const filterLibIds = new Set();
+        _.each(filterLibAndVersion, (lib) => {
+            filterLibIds.add(lib.id);
+        });
+
+        const copiedLibraries = {};
+        _.each(allLibraries, (lib, libid) => {
+            if (!filterLibIds.has(libid)) return;
+
+            const libcopy = Object.assign({}, lib);
+            libcopy.versions = _.omit(lib.versions, (version, versionid) => {
+                for (let filterIdx = 0; filterIdx < filterLibAndVersion.length; filterIdx++) {
+                    const filter = filterLibAndVersion[filterIdx];
+                    if (filter.id === libid) {
+                        if (!filter.version) return false;
+                        if (filter.version === versionid) return false;
+                    }
+                }
+
+                return true;
+            });
+
+            copiedLibraries[libid] = libcopy;
+        });
+
+        return copiedLibraries;
+    }
+
     async compilerConfigFor(langId, compilerName, parentProps) {
         const base = `compiler.${compilerName}.`;
 
@@ -176,6 +220,16 @@ class CompilerFinder {
         const options = props("options", "");
         let actualOptions = _.compact([baseOptions, options]).join(' ');
 
+        let supportedLibraries = null;
+
+        const supportsLibrariesSetting = _.filter(props("supportsLibraries", "").split(":"), (a) => a !== "");
+        if (supportsLibrariesSetting.length > 0) {
+            const libs = this.optionsHandler.get().libs[langId];
+            supportedLibraries = this.copyAndFilterLibraries(libs, supportsLibrariesSetting);
+        } else {
+            supportedLibraries = this.optionsHandler.get().libs[langId];
+        }
+
         const envVars = (() => {
             const envVarsString = props("envVars", "");
             if (envVarsString === "") {
@@ -225,7 +279,7 @@ class CompilerFinder {
             notification: props("notification", ""),
             isSemVer: isSemVer,
             semver: semverVer,
-            libs: this.optionsHandler.get().libs[langId],
+            libs: supportedLibraries,
             tools: _.omit(this.optionsHandler.get().tools[langId], tool => tool.isCompilerExcluded(compilerName)),
             unwiseOptions: props("unwiseOptions", "").split("|")
         };
diff --git a/views/index.pug b/views/index.pug
index e81c9d25b..88eef6890 100644
--- a/views/index.pug
+++ b/views/index.pug
@@ -2,7 +2,7 @@ extends _layout.pug
 
 block prepend content
   nav.navbar.navbar-godbolt.navbar-expand-md.navbar-light.bg-light
-    a.navbar-brand(href="javascript:;" title="Compiler Explorer")
+    a.navbar-brand(href=httpRoot title="Compiler Explorer")
       svg.logo.align-top(height="50" width="165")
         include resources/site-logo.svg
         if extraBodyClass === "beta"


Test patch:
diff --git a/test/compiler-finder-tests.js b/test/compiler-finder-tests.js
index 7c3764e24..e40d18a5f 100644
--- a/test/compiler-finder-tests.js
+++ b/test/compiler-finder-tests.js
@@ -36,6 +36,31 @@ const languages = {
     }
 };
 
+const libs = {
+    'a-lang': {
+        fmt: {
+            versions: {
+                trunk: {
+                    version: "(trunk)",
+                    libPath: "/fmt/trunk/lib"
+                }
+            }
+        },
+        catch2: {
+            versions: {
+                2101: {
+                    version: "2.1.0.1",
+                    libPath: "/catch2/2.1.0.1/lib/x86_64"
+                },
+                2102: {
+                    version: "2.1.0.2",
+                    libPath: "/catch2/2.1.0.2/lib/x86_64"
+                }
+            }
+        }
+    }
+};
+
 const props = {
     compilers: "goodCompiler:&badCompiler"
 };
@@ -60,6 +85,11 @@ const bothOptions = {
     options: "bar"
 };
 
+const supportsLibrariesOptions = {
+    compilers: "goodCompiler",
+    supportsLibraries: "fmt:catch2.2101"
+};
+
 describe('Compiler-finder', function () {
     let compilerProps;
 
@@ -67,6 +97,7 @@ describe('Compiler-finder', function () {
     let noBaseOptionsProps;
     let onlyBaseOptionsProps;
     let bothOptionsProps;
+    let libraryCompilerProps;
 
     let optionsHandler;
 
@@ -78,10 +109,12 @@ describe('Compiler-finder', function () {
         onlyBaseOptionsProps = new properties.CompilerProps(languages, properties.fakeProps(onlyBaseOptions));
         bothOptionsProps = new properties.CompilerProps(languages, properties.fakeProps(bothOptions));
 
+        libraryCompilerProps = new properties.CompilerProps(languages, properties.fakeProps(supportsLibrariesOptions));
+
         optionsHandler = {
             get: () => {
                 return {
-                    libs: {},
+                    libs: libs,
                     tools: {}
                 };
             }
@@ -117,4 +150,28 @@ describe('Compiler-finder', function () {
         const compilers = await finder.getCompilers();
         compilers[0].options.should.equal('foo bar');
     });
+
+    it('should be able to filter libraries', async () => {
+        const finder = new CompilerFinder({}, libraryCompilerProps, properties.fakeProps({}), {}, optionsHandler);
+        const compilers = await finder.getCompilers();
+        const libs = compilers[0].libs;
+        libs.should.deep.equal({
+            catch2: {
+                versions: {
+                    2101: {
+                        version: "2.1.0.1",
+                        libPath: "/catch2/2.1.0.1/lib/x86_64"
+                    }
+                }
+            },
+            fmt: {
+                versions: {
+                    trunk: {
+                        version: "(trunk)",
+                        libPath: "/fmt/trunk/lib"
+                    }
+                }
+            }
+        });
+    });
 });