From 51dbe0407f2ad9086672f517150e72a1e8aa2a21 Mon Sep 17 00:00:00 2001 From: AJADHAV Date: Thu, 4 Jun 2026 15:30:48 +0530 Subject: [PATCH 1/4] build: fix macOS/Windows cross-platform build without SAP SDK --- apps/openadt-bootstrap/project.json | 6 +++--- apps/openadt-cli/project.json | 6 +++--- apps/openadt-config/project.json | 6 +++--- apps/openadt-sap-adt/project.json | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/openadt-bootstrap/project.json b/apps/openadt-bootstrap/project.json index b5ea995a..9e2e495c 100644 --- a/apps/openadt-bootstrap/project.json +++ b/apps/openadt-bootstrap/project.json @@ -11,7 +11,7 @@ "executor": "nx:run-commands", "dependsOn": ["openadt-config:compile", "openadt-sap-adt:compile"], "options": { - "command": "node scripts/mvnw-runner.mjs -q compile -pl apps/openadt-bootstrap -am -f pom.xml", + "command": "node scripts/mvnw-runner.mjs -q install -pl apps/openadt-bootstrap -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml", "cwd": "{workspaceRoot}" }, "cache": true, @@ -22,7 +22,7 @@ "executor": "nx:run-commands", "dependsOn": ["compile"], "options": { - "command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-bootstrap -am -f pom.xml", + "command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-bootstrap -Pdistribution -Dopenadt.distribution=true -f pom.xml && node scripts/mvnw-runner.mjs install -pl apps/openadt-bootstrap -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml", "cwd": "{workspaceRoot}" }, "cache": true, @@ -37,7 +37,7 @@ "openadt-sap-adt:package" ], "options": { - "command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-bootstrap -am -DskipTests -f pom.xml", + "command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-bootstrap -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml", "cwd": "{workspaceRoot}" }, "cache": true, diff --git a/apps/openadt-cli/project.json b/apps/openadt-cli/project.json index 363e42ef..ec78f4df 100644 --- a/apps/openadt-cli/project.json +++ b/apps/openadt-cli/project.json @@ -15,7 +15,7 @@ "openadt-bootstrap:compile" ], "options": { - "command": "node scripts/mvnw-runner.mjs -q compile -pl apps/openadt-cli -am -Pdistribution -Dopenadt.distribution=true -f pom.xml && bun run scripts/dev-runtime-classpath.ts", + "command": "node scripts/mvnw-runner.mjs -q install -pl apps/openadt-cli -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml && bun run scripts/dev-runtime-classpath.ts", "cwd": "{workspaceRoot}" }, "cache": true, @@ -26,7 +26,7 @@ "executor": "nx:run-commands", "dependsOn": ["package"], "options": { - "command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-cli -am -Pdistribution -Dopenadt.distribution=true -f pom.xml", + "command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-cli -Pdistribution -Dopenadt.distribution=true -f pom.xml", "cwd": "{workspaceRoot}" }, "cache": true, @@ -36,7 +36,7 @@ "package": { "executor": "nx:run-commands", "options": { - "command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-cli -am -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml", + "command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-cli -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml", "cwd": "{workspaceRoot}" }, "cache": true, diff --git a/apps/openadt-config/project.json b/apps/openadt-config/project.json index 547bb90b..e9ac5bb3 100644 --- a/apps/openadt-config/project.json +++ b/apps/openadt-config/project.json @@ -10,7 +10,7 @@ "compile": { "executor": "nx:run-commands", "options": { - "command": "node scripts/mvnw-runner.mjs -q compile -pl apps/openadt-config -f pom.xml", + "command": "node scripts/mvnw-runner.mjs -q install -N -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml && node scripts/mvnw-runner.mjs -q install -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml", "cwd": "{workspaceRoot}" }, "cache": true, @@ -21,7 +21,7 @@ "executor": "nx:run-commands", "dependsOn": ["compile"], "options": { - "command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-config -f pom.xml", + "command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -f pom.xml && node scripts/mvnw-runner.mjs -q install -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml", "cwd": "{workspaceRoot}" }, "cache": true, @@ -32,7 +32,7 @@ "executor": "nx:run-commands", "dependsOn": ["compile"], "options": { - "command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-config -DskipTests -f pom.xml", + "command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml", "cwd": "{workspaceRoot}" }, "cache": true, diff --git a/apps/openadt-sap-adt/project.json b/apps/openadt-sap-adt/project.json index 1ebc3d2b..cdb8989f 100644 --- a/apps/openadt-sap-adt/project.json +++ b/apps/openadt-sap-adt/project.json @@ -11,7 +11,7 @@ "executor": "nx:run-commands", "dependsOn": ["openadt-config:compile"], "options": { - "command": "node scripts/mvnw-runner.mjs -q compile -pl apps/openadt-sap-adt -am -f pom.xml", + "command": "node scripts/mvnw-runner.mjs -q install -pl apps/openadt-sap-adt -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml", "cwd": "{workspaceRoot}" }, "cache": true, @@ -22,7 +22,7 @@ "executor": "nx:run-commands", "dependsOn": ["compile"], "options": { - "command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-sap-adt -am -f pom.xml", + "command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-sap-adt -Pdistribution -Dopenadt.distribution=true -f pom.xml && node scripts/mvnw-runner.mjs install -pl apps/openadt-sap-adt -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml", "cwd": "{workspaceRoot}" }, "cache": true, @@ -33,7 +33,7 @@ "executor": "nx:run-commands", "dependsOn": ["compile", "openadt-config:package"], "options": { - "command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-sap-adt -am -DskipTests -f pom.xml", + "command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-sap-adt -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml", "cwd": "{workspaceRoot}" }, "cache": true, From a029df2a4b6643ad20015d936c4e85fa3d872069 Mon Sep 17 00:00:00 2001 From: AJADHAV Date: Thu, 4 Jun 2026 17:09:43 +0530 Subject: [PATCH 2/4] feat(bootstrap): add macOS SAP GUI for Java landscape support Add support for SAP GUI for Java landscape files on macOS: - Detect ~/Library/Preferences/SAP/SAPGUILandscape.xml - Detect external landscape URLs from tags - Warn users when message server details may be in external landscape - Update spec to document macOS paths and external landscape behavior Also includes previous Eclipse.app bundle support for JCo detection. --- .../bootstrap/SapGuiLandscapeDetector.java | 14 ++++++ .../openadt/bootstrap/SetupPathLocator.java | 43 +++++++++++++++++++ specs/setup.md | 3 ++ 3 files changed, 60 insertions(+) diff --git a/apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SapGuiLandscapeDetector.java b/apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SapGuiLandscapeDetector.java index 03018f7f..d3d8371a 100644 --- a/apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SapGuiLandscapeDetector.java +++ b/apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SapGuiLandscapeDetector.java @@ -52,12 +52,26 @@ private List parseLandscapeFile(Path path) throws IOException { List systems = new ArrayList<>(); parseClassicSystems(document, systems); parseLoadBalancedSystems(document, systems); + detectExternalLandscapeUrls(document, path); return systems; } catch (ParserConfigurationException | SAXException e) { throw new IOException("Failed to parse SAP GUI landscape: " + path, e); } } + private void detectExternalLandscapeUrls(Document document, Path sourcePath) { + NodeList includeNodes = document.getElementsByTagName("Include"); + for (int i = 0; i < includeNodes.getLength(); i++) { + Element include = (Element) includeNodes.item(i); + String url = blankToNull(include.getAttribute("url")); + if (url != null) { + System.err.println("INFO: External SAP landscape URL detected: " + url); + System.err.println("INFO: Some system details (message server hostnames) may be in the external landscape."); + System.err.println("INFO: Open SAP GUI once to cache the full landscape, or provide message server manually."); + } + } + } + private void parseClassicSystems(Document document, List systems) { NodeList systemNodes = document.getElementsByTagName("System"); for (int i = 0; i < systemNodes.getLength(); i++) { diff --git a/apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java b/apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java index 36b34bcd..c228128e 100644 --- a/apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java +++ b/apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java @@ -36,6 +36,8 @@ static List sapGuiLandscapeFiles() { } } else if (os.contains("mac")) { paths.add(Path.of(home, "Library", "Application Support", "SAP", SAP_COMMON, SAPUI_LANDSCAPE)); + // SAP GUI for Java stores landscape in Preferences with different filename + paths.add(Path.of(home, "Library", "Preferences", "SAP", "SAPGUILandscape.xml")); } for (Path windowsHome : windowsUserHomes()) { @@ -103,6 +105,23 @@ static List eclipseWorkspacePaths() { static List jcoJarRoots() { List paths = new ArrayList<>(); + String os = System.getProperty(OS_NAME_PROPERTY, "").toLowerCase(Locale.ROOT); + String home = System.getProperty(USER_HOME_PROPERTY, ""); + + // macOS and Linux: check user's home directory for Eclipse p2 pool + if (!home.isBlank() && (os.contains("mac") || os.contains("nix") || os.contains("nux"))) { + paths.add(Path.of(home, ".p2", "pool", "plugins")); + } + + // macOS: Eclipse.app bundle installations + if (os.contains("mac")) { + paths.add(Path.of("/Applications", "Eclipse.app", "Contents", "Eclipse", "plugins")); + paths.add(Path.of("/Applications", "SAP Business Application Studio.app", "Contents", "Eclipse", "plugins")); + if (!home.isBlank()) { + paths.add(Path.of(home, "Applications", "Eclipse.app", "Contents", "Eclipse", "plugins")); + } + } + for (Path windowsHome : windowsUserHomes()) { paths.add(windowsHome.resolve(".p2/pool/plugins")); } @@ -113,6 +132,20 @@ static List jcoJarRoots() { static List jcoNativeSearchRoots() { List paths = new ArrayList<>(); + String os = System.getProperty(OS_NAME_PROPERTY, "").toLowerCase(Locale.ROOT); + String home = System.getProperty(USER_HOME_PROPERTY, ""); + + // macOS-specific paths for JCo native libraries + if (os.contains("mac") && !home.isBlank()) { + paths.add(Path.of(home, "lib")); + paths.add(Path.of(home, ".p2")); + paths.add(Path.of("/usr", "local", "lib")); + paths.add(Path.of("/Library", "Java", "Extensions")); + // Eclipse.app OSGi bundle cache (where JCo native lib is extracted) + paths.add(Path.of("/Applications", "Eclipse.app", "Contents", "Eclipse", "configuration", "org.eclipse.osgi")); + paths.add(Path.of(home, "Applications", "Eclipse.app", "Contents", "Eclipse", "configuration", "org.eclipse.osgi")); + } + for (Path windowsHome : windowsUserHomes()) { paths.add(windowsHome.resolve("Documents")); paths.add(windowsHome.resolve("AppData/Local")); @@ -144,6 +177,16 @@ private static Path openadtSdkRoot() { static List sapcryptoCandidates() { List paths = new ArrayList<>(); + String os = System.getProperty(OS_NAME_PROPERTY, "").toLowerCase(Locale.ROOT); + String home = System.getProperty(USER_HOME_PROPERTY, ""); + + // macOS-specific paths for SAP Crypto + if (os.contains("mac") && !home.isBlank()) { + paths.add(Path.of(home, "lib", "libsapcrypto.dylib")); + paths.add(Path.of("/usr", "local", "lib", "libsapcrypto.dylib")); + paths.add(Path.of("/Library", "Frameworks", "sapcrypto.framework", "Versions", "Current", "libsapcrypto.dylib")); + } + for (Path programFilesRoot : windowsProgramFilesRoots()) { paths.add(programFilesRoot.resolve("SAP/FrontEnd/SecureLogin/lib/sapcrypto.dll")); } diff --git a/specs/setup.md b/specs/setup.md index 1c9ca067..cf1d9522 100644 --- a/specs/setup.md +++ b/specs/setup.md @@ -42,9 +42,12 @@ Lookup paths: - **Windows**: `%APPDATA%\SAP\Common\SAPUILandscape.xml` - **macOS**: `~/Library/Application Support/SAP/Common/SAPUILandscape.xml` +- **macOS (SAP GUI for Java)**: `~/Library/Preferences/SAP/SAPGUILandscape.xml` - **WSL**: `/mnt/c/Users//AppData/Roaming/SAP/Common/SAPUILandscape.xml` - **WSL**: `/mnt/c/Users//AppData/Roaming/SAP/LogonServerConfigCache/*.xml` +When the landscape file contains `` tags pointing to external landscapes (common with centrally-managed SAP GUI deployments), the detector logs an informational message. Full system details (including message server hostnames) may require opening SAP GUI once to cache the landscape locally, or manual configuration of the `mshost` field. + For classic `` entries, extracts: - `name` → alias, description From 9d1480826023d33478227b278124ca3ad3088627 Mon Sep 17 00:00:00 2001 From: AJADHAV Date: Sat, 6 Jun 2026 15:31:53 +0530 Subject: [PATCH 3/4] feat(bootstrap): detect SAP GUI cached landscapes with message servers - Add detection for ~/Library/Preferences/SAP/Cache/SAPUILandscape/*.xml - Extract full message server details from cached landscapes - Update S0D config: s0d-cs.sap.booking.com:3600 - Update S0A config: s0a-cs.sap.booking.com:3600 - Now detects 50+ systems automatically from cache Note: RFC connection requires SNC/sapcrypto configuration --- .../java/org/openadt/bootstrap/SetupPathLocator.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java b/apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java index c228128e..6db650d7 100644 --- a/apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java +++ b/apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java @@ -38,6 +38,16 @@ static List sapGuiLandscapeFiles() { paths.add(Path.of(home, "Library", "Application Support", "SAP", SAP_COMMON, SAPUI_LANDSCAPE)); // SAP GUI for Java stores landscape in Preferences with different filename paths.add(Path.of(home, "Library", "Preferences", "SAP", "SAPGUILandscape.xml")); + // SAP GUI for Java caches full landscape with message servers + Path cacheDir = Path.of(home, "Library", "Preferences", "SAP", "Cache", "SAPUILandscape"); + if (Files.exists(cacheDir)) { + try (var stream = Files.list(cacheDir)) { + stream.filter(f -> f.toString().endsWith(".xml")) + .forEach(paths::add); + } catch (IOException e) { + // Ignore cache read errors + } + } } for (Path windowsHome : windowsUserHomes()) { From 0c697670c167617aa378a3dc3be2294d8ed5034c Mon Sep 17 00:00:00 2001 From: AJADHAV Date: Sat, 6 Jun 2026 21:11:08 +0530 Subject: [PATCH 4/4] feat(securelogin): add macOS Secure Login API v2 fallback - Modify SecureLoginHubClient to try API v3 (Windows) first - Fall back to API v2 (macOS) if v3 fails - Note: macOS Secure Login Client uses incompatible API - Requires further investigation for full macOS SNC support Related: macOS Secure Login hub returns 'Only requests in SLC namespace supported' --- .../adt/bootstrap/SecureLoginHubClient.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/apps/openadt-sap-adt/src/main/java/org/openadt/sap/adt/bootstrap/SecureLoginHubClient.java b/apps/openadt-sap-adt/src/main/java/org/openadt/sap/adt/bootstrap/SecureLoginHubClient.java index 0fb1839a..a06fcaf2 100644 --- a/apps/openadt-sap-adt/src/main/java/org/openadt/sap/adt/bootstrap/SecureLoginHubClient.java +++ b/apps/openadt-sap-adt/src/main/java/org/openadt/sap/adt/bootstrap/SecureLoginHubClient.java @@ -88,6 +88,15 @@ public boolean isReachable() { public String webAdapterStatus(String profileId) throws IOException, InterruptedException { String encodedProfileId = URLEncoder.encode(profileId, StandardCharsets.UTF_8); + // Try API v3 first (Windows), fall back to v2 (macOS) + try { + return webAdapterStatusV3(encodedProfileId); + } catch (IOException e) { + return webAdapterStatusV2(encodedProfileId); + } + } + + private String webAdapterStatusV3(String encodedProfileId) throws IOException, InterruptedException { URI uri = URI.create(hubBaseUrl + "/slc3/api/status?profileid=" + encodedProfileId); HttpResponse response = httpClient.send( hubRequest(uri).GET().build(), @@ -101,11 +110,34 @@ public String webAdapterStatus(String profileId) throws IOException, Interrupted return status.isTextual() ? status.asText() : "UNKNOWN"; } + private String webAdapterStatusV2(String encodedProfileId) throws IOException, InterruptedException { + URI uri = URI.create(hubBaseUrl + "/slc2/api/status?profileid=" + encodedProfileId); + HttpResponse response = httpClient.send( + hubRequest(uri).GET().build(), + HttpResponse.BodyHandlers.ofString() + ); + if (response.statusCode() != 200) { + throw new IOException("Secure Login hub v2 status failed with HTTP " + response.statusCode()); + } + JsonNode root = objectMapper.readTree(response.body()); + JsonNode status = root.path("status"); + return status.isTextual() ? status.asText() : "UNKNOWN"; + } + public void loginWebAdapter(String profileId) throws IOException, InterruptedException { loginWebAdapter(profileId, false); } public void loginWebAdapter(String profileId, boolean browserMonitor) throws IOException, InterruptedException { + // Try API v3 first (Windows), fall back to v2 (macOS) + try { + loginWebAdapterV3(profileId, browserMonitor); + } catch (IOException e) { + loginWebAdapterV2(profileId, browserMonitor); + } + } + + private void loginWebAdapterV3(String profileId, boolean browserMonitor) throws IOException, InterruptedException { URI uri = URI.create(hubBaseUrl + "/slc3/api/login"); java.util.Map clientConfig = new java.util.LinkedHashMap<>(); clientConfig.put("browserMonitor", browserMonitor); @@ -130,6 +162,31 @@ public void loginWebAdapter(String profileId, boolean browserMonitor) throws IOE } } + private void loginWebAdapterV2(String profileId, boolean browserMonitor) throws IOException, InterruptedException { + URI uri = URI.create(hubBaseUrl + "/slc2/api/login"); + java.util.Map clientConfig = new java.util.LinkedHashMap<>(); + clientConfig.put("browserMonitor", browserMonitor); + clientConfig.put("inactivityTimeout", 0); + clientConfig.put("keySize", 2048); + clientConfig.put("profileSLWA", profileId); + clientConfig.put("localport", 34443); + clientConfig.put("autoLogOut", false); + java.util.Map payload = new java.util.LinkedHashMap<>(); + payload.put("profileid", profileId); + payload.put("clientConfig", clientConfig); + String body = objectMapper.writeValueAsString(payload); + HttpResponse response = httpClient.send( + hubRequest(uri) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(), + HttpResponse.BodyHandlers.ofString() + ); + if (response.statusCode() < 200 || response.statusCode() >= 300) { + throw new IOException("Secure Login hub v2 login failed with HTTP " + response.statusCode()); + } + } + public void ensureWebAdapterLoggedIn(String profileId) throws IOException, InterruptedException { ensureWebAdapterLoggedIn(profileId, false); }