Repository: compiler-explorer/compiler-explorer (ts)

Issue:
[BUG]: Libraries not available for C++ compilers on GPU instances
It seems that libraries are not available for nvc++ compilers.

Logging from (normal amd64 instance) startup:
```
info: Fetching remote libraries from https://godbolt.org:443/winprod/api/libraries/c++ 
info: Fetching remote libraries from https://godbolt.org:443/winprod/api/libraries/c 
info: Fetching remote libraries from https://godbolt.org:443/gpu/api/libraries/cuda
```

It is expected to also show fetching libraries from `https://godbolt.org:443/gpu/api/libraries/c++`, but that does not happen for some reason.


Pull request description:
fix remote libraries listing backend and frontend
Fixes #7590

Ugly code duplication was a bit annoying

Patch:
diff --git a/CLAUDE.md b/CLAUDE.md
index 5db914d0b..4d86679c5 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -47,16 +47,6 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
   // Initialises the thing
   initialiseThing();
   ```
-- Avoid redundant function header comments that merely repeat the function name. For example:
-  ```
-  /**
-   * Sets up compiler change handling
-   */
-  function setupCompilerChangeHandling() {...}
-  ```
-  In this case, the function name already clearly states what it does.
-- Comments should provide additional context or explain "why" something is done, not just restate "what" is being done.
-- Only add function header comments when they provide meaningful information beyond what the function name and signature convey.
 - Use British English spellings for things like "initialise" and "colour", but only in new code. It's a preference not a hard requirement
 
 ## Testing Guidelines
diff --git a/etc/config/kotlin.amazon.properties b/etc/config/kotlin.amazon.properties
index 838c36f64..5ea01b1bb 100644
--- a/etc/config/kotlin.amazon.properties
+++ b/etc/config/kotlin.amazon.properties
@@ -131,19 +131,3 @@ compiler.kotlinc2100.exe=/opt/compiler-explorer/kotlin-jvm-2.1.0/bin/kotlinc-jvm
 compiler.kotlinc2100.semver=2.1.0
 compiler.kotlinc2100.java_home=/opt/compiler-explorer/jdk-23.0.1
 compiler.kotlinc2100.runtime=/opt/compiler-explorer/jdk-23.0.1/bin/java
-
-libs=kotlinx-coroutines-core-jvm
-
-libs.kotlinx-coroutines-core-jvm.name=kotlinx.coroutines
-libs.kotlinx-coroutines-core-jvm.versions=1102:190:181:173:164
-
-libs.kotlinx-coroutines-core-jvm.versions.1102.version=1.10.2
-libs.kotlinx-coroutines-core-jvm.versions.1102.path=/opt/compiler-explorer/kotlinx-coroutines-core-jvm-1.10.2/kotlinx-coroutines-core-jvm.jar
-libs.kotlinx-coroutines-core-jvm.versions.190.version=1.9.0
-libs.kotlinx-coroutines-core-jvm.versions.190.path=/opt/compiler-explorer/kotlinx-coroutines-core-jvm-1.9.0/kotlinx-coroutines-core-jvm.jar
-libs.kotlinx-coroutines-core-jvm.versions.181.version=1.8.1
-libs.kotlinx-coroutines-core-jvm.versions.181.path=/opt/compiler-explorer/kotlinx-coroutines-core-jvm-1.8.1/kotlinx-coroutines-core-jvm.jar
-libs.kotlinx-coroutines-core-jvm.versions.173.version=1.7.3
-libs.kotlinx-coroutines-core-jvm.versions.173.path=/opt/compiler-explorer/kotlinx-coroutines-core-jvm-1.7.3/kotlinx-coroutines-core-jvm.jar
-libs.kotlinx-coroutines-core-jvm.versions.164.version=1.6.4
-libs.kotlinx-coroutines-core-jvm.versions.164.path=/opt/compiler-explorer/kotlinx-coroutines-core-jvm-1.6.4/kotlinx-coroutines-core-jvm.jar
diff --git a/lib/options-handler.ts b/lib/options-handler.ts
index 6b477d350..fb8bf15fe 100755
--- a/lib/options-handler.ts
+++ b/lib/options-handler.ts
@@ -37,6 +37,7 @@ import type {Source} from '../types/source.interfaces.js';
 import type {ToolTypeKey} from '../types/tool.interfaces.js';
 import {AppArguments} from './app.interfaces.js';
 
+import {getRemoteId} from '../shared/remote-utils.js';
 import {logger} from './logger.js';
 import type {PropertyGetter, PropertyValue} from './properties.interfaces.js';
 import {CompilerProps} from './properties.js';
@@ -376,11 +377,6 @@ export class ClientOptionsHandler {
         return libraries;
     }
 
-    getRemoteId(remoteUrl: string, language: LanguageKey) {
-        const url = new URL(remoteUrl);
-        return url.host.replaceAll('.', '_') + '_' + language;
-    }
-
     libArrayToObject(libsArr: any[]) {
         const libs: Record<string, any> = {};
         for (const lib of libsArr) {
@@ -397,9 +393,9 @@ export class ClientOptionsHandler {
     }
 
     async getRemoteLibraries(language: LanguageKey, remoteUrl: string) {
-        const remoteId = this.getRemoteId(remoteUrl, language);
+        const remoteId = getRemoteId(remoteUrl, language);
         if (!this.remoteLibs[remoteId]) {
-            return new Promise(resolve => {
+            return await new Promise(resolve => {
                 const url = ClientOptionsHandler.getRemoteUrlForLibraries(remoteUrl, language);
                 logger.info(`Fetching remote libraries from ${url}`);
                 let fullData = '';
diff --git a/shared/remote-utils.ts b/shared/remote-utils.ts
new file mode 100644
index 000000000..23b5c083b
--- /dev/null
+++ b/shared/remote-utils.ts
@@ -0,0 +1,28 @@
+// Copyright (c) 2025, Compiler Explorer Authors
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright
+//       notice, this list of conditions and the following disclaimer in the
+//       documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+export function getRemoteId(remoteUrl: string, language: string): string {
+    const url: URL = new URL(remoteUrl);
+    return url.host.replace(/\./g, '_') + url.pathname.replace(/\//g, '_') + '_' + language;
+}
diff --git a/static/lib-utils.ts b/static/lib-utils.ts
index 88f111c35..f1a5b2b68 100644
--- a/static/lib-utils.ts
+++ b/static/lib-utils.ts
@@ -22,19 +22,15 @@
 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 // POSSIBILITY OF SUCH DAMAGE.
 
+import {getRemoteId} from '../shared/remote-utils.js';
 import {Remote} from './compiler.interfaces.js';
 import {LanguageLibs, Library} from './options.interfaces.js';
 import {options} from './options.js';
 
 const LIB_MATCH_RE = /([\w-]*)\.([\w-]*)/i;
 
-function getRemoteId(language: string, remoteUrl: string): string {
-    const url: URL = new URL(remoteUrl);
-    return url.host.replace(/\./g, '_') + '_' + language;
-}
-
 function getRemoteLibraries(language: string, remoteUrl: string): LanguageLibs {
-    const remoteId = getRemoteId(language, remoteUrl);
+    const remoteId = getRemoteId(remoteUrl, language);
     return options.remoteLibs[remoteId];
 }
 
@@ -78,7 +74,7 @@ export function getSupportedLibraries(
         }
         return allLibs;
     }
-    const allLibs = getRemoteLibraries(langId, remote.target);
+    const allLibs = getRemoteLibraries(langId, remote.target + remote.basePath);
     if (supportedLibrariesArr && supportedLibrariesArr.length > 0) {
         return copyAndFilterLibraries(allLibs, supportedLibrariesArr);
     }


Test patch:
diff --git a/cypress/e2e/remote-id.cy.ts b/cypress/e2e/remote-id.cy.ts
new file mode 100644
index 000000000..c043eca90
--- /dev/null
+++ b/cypress/e2e/remote-id.cy.ts
@@ -0,0 +1,9 @@
+import {runFrontendTest} from '../support/utils';
+
+describe('RemoteId testing', () => {
+    before(() => {
+        cy.visit('/');
+    });
+
+    runFrontendTest('remoteId');
+});
diff --git a/shared/url-testcases.ts b/shared/url-testcases.ts
new file mode 100644
index 000000000..dc3616891
--- /dev/null
+++ b/shared/url-testcases.ts
@@ -0,0 +1,32 @@
+export const UrlTestCases = [
+    {
+        remoteUrl: 'https://example.com/path/to/resource',
+        language: 'cpp',
+        expectedId: 'example_com_path_to_resource_cpp',
+    },
+    {
+        remoteUrl: 'https://example.com',
+        language: 'java',
+        expectedId: 'example_com__java',
+    },
+    {
+        remoteUrl: 'https://sub.domain.com/a/b/c/',
+        language: 'rust',
+        expectedId: 'sub_domain_com_a_b_c__rust',
+    },
+    {
+        remoteUrl: 'https://godbolt.org:443/gpu',
+        language: 'c++',
+        expectedId: 'godbolt_org_gpu_c++',
+    },
+    {
+        remoteUrl: 'https://godbolt.org:443/winprod',
+        language: 'c++',
+        expectedId: 'godbolt_org_winprod_c++',
+    },
+    {
+        remoteUrl: 'https://godbolt.org:443',
+        language: 'c++',
+        expectedId: 'godbolt_org__c++',
+    },
+];
diff --git a/static/tests/_all.ts b/static/tests/_all.ts
index 0f2f1ec18..6c9b96803 100644
--- a/static/tests/_all.ts
+++ b/static/tests/_all.ts
@@ -25,3 +25,4 @@
 import './frontend-testing';
 import './hello-world';
 import './motd';
+import './remote-id';
diff --git a/static/tests/remote-id.ts b/static/tests/remote-id.ts
new file mode 100644
index 000000000..3d86d6b78
--- /dev/null
+++ b/static/tests/remote-id.ts
@@ -0,0 +1,47 @@
+// Copyright (c) 2025, Compiler Explorer Authors
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//     * Redistributions of source code must retain the above copyright notice,
+//       this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above copyright
+//       notice, this list of conditions and the following disclaimer in the
+//       documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+/// <reference path="../../node_modules/cypress/types/cypress-global-vars.d.ts" />
+
+import {getRemoteId} from '../../shared/remote-utils.js';
+
+import {UrlTestCases} from '../../shared/url-testcases.js';
+
+import {ITestable} from './frontend-testing.interfaces.js';
+
+class RemoteIdTests implements ITestable {
+    public readonly description: string = 'remoteId';
+
+    public async run() {
+        UrlTestCases.forEach(testCase => {
+            if (getRemoteId(testCase.remoteUrl, testCase.language) !== testCase.expectedId) {
+                throw new Error(
+                    `Test case failed for language: ${testCase.language}, remoteUrl: ${testCase.remoteUrl}, expectedId: ${testCase.expectedId}`,
+                );
+            }
+        });
+    }
+}
+
+window.compilerExplorerFrontendTesting.add(new RemoteIdTests());
diff --git a/test/options-handler.ts b/test/options-handler.ts
index a8d4653dc..8e36e18b7 100644
--- a/test/options-handler.ts
+++ b/test/options-handler.ts
@@ -37,6 +37,9 @@ import {BaseTool} from '../lib/tooling/base-tool.js';
 import {CompilerInfo} from '../types/compiler.interfaces.js';
 import {LanguageKey} from '../types/languages.interfaces.js';
 
+import {getRemoteId} from '../shared/remote-utils.js';
+import {UrlTestCases} from '../shared/url-testcases.js';
+
 import {makeFakeCompilerInfo} from './utils.js';
 
 const languages = {
@@ -593,4 +596,13 @@ describe('Options handler', () => {
 
         expect(librariesUrl).toEqual('https://godbolt.org:443/gpu/api/libraries/c++');
     });
+
+    describe('getRemoteId', () => {
+        UrlTestCases.forEach(testCase => {
+            it(`should generate remote ID for URL "${testCase.remoteUrl}" with language "${testCase.language}"`, () => {
+                const result = getRemoteId(testCase.remoteUrl, testCase.language);
+                expect(result).toBe(testCase.expectedId);
+            });
+        });
+    });
 });