11// SPDX-License-Identifier: Apache-2.0
22
33// Package docker implements Docker build and deployment support for Mendix projects.
4+ //
5+ // # Platform differences for Mendix tool resolution
6+ //
7+ // On Windows, Studio Pro installs mxbuild.exe, mx.exe, and the runtime under
8+ // a Program Files directory (e.g., D:\Program Files\Mendix\11.6.4\).
9+ // CDN downloads (mxbuild tar.gz) contain Linux ELF binaries that cannot
10+ // execute on Windows, so Studio Pro installations MUST be preferred.
11+ //
12+ // On Linux/macOS (CI, devcontainers), Studio Pro is not available.
13+ // CDN downloads are the primary source for mxbuild and runtime.
14+ //
15+ // Resolution priority (all platforms):
16+ // 1. Explicit path (--mxbuild-path)
17+ // 2. PATH lookup
18+ // 3. OS-specific known locations (Studio Pro on Windows)
19+ // 4. Cached CDN downloads (~/.mxcli/mxbuild/)
20+ //
21+ // Path discovery on Windows must NOT hardcode drive letters. Use environment
22+ // variables (PROGRAMFILES, PROGRAMW6432, SystemDrive) to locate install dirs.
423package docker
524
625import (
@@ -49,7 +68,9 @@ func findMxBuildInDir(dir string) string {
4968}
5069
5170// resolveMxBuild finds the MxBuild executable.
52- // Priority: explicit path > PATH lookup > OS-specific known locations.
71+ // Priority: explicit path > PATH lookup > OS-specific known locations > cached downloads.
72+ // On Windows, Studio Pro installations are checked before cached downloads because
73+ // CDN downloads are Linux binaries that cannot run natively on Windows.
5374// The explicit path can be the binary itself or a directory containing it
5475// (e.g., a Mendix installation root with modeler/mxbuild inside).
5576func resolveMxBuild (explicitPath string ) (string , error ) {
@@ -73,12 +94,8 @@ func resolveMxBuild(explicitPath string) (string, error) {
7394 return p , nil
7495 }
7596
76- // Try cached downloads (~/.mxcli/mxbuild/*/modeler/mxbuild)
77- if p := AnyCachedMxBuildPath (); p != "" {
78- return p , nil
79- }
80-
81- // Try OS-specific known locations
97+ // Try OS-specific known locations (Studio Pro on Windows) BEFORE cached downloads.
98+ // On Windows, CDN downloads are Linux binaries — Studio Pro's mxbuild.exe is preferred.
8299 for _ , pattern := range mxbuildSearchPaths () {
83100 matches , _ := filepath .Glob (pattern )
84101 if len (matches ) > 0 {
@@ -87,33 +104,83 @@ func resolveMxBuild(explicitPath string) (string, error) {
87104 }
88105 }
89106
107+ // Try cached downloads (~/.mxcli/mxbuild/*/modeler/mxbuild)
108+ if p := AnyCachedMxBuildPath (); p != "" {
109+ return p , nil
110+ }
111+
90112 return "" , fmt .Errorf ("mxbuild not found; install Mendix Studio Pro or specify --mxbuild-path" )
91113}
92114
93- // mxbuildSearchPaths returns OS-specific glob patterns for MxBuild.
94- func mxbuildSearchPaths () []string {
115+ // ResolveStudioProDir finds the Studio Pro installation directory for a specific
116+ // Mendix version on Windows. Returns the installation root (e.g.,
117+ // "D:\Program Files\Mendix\11.6.4") or empty string if not found.
118+ // On non-Windows platforms, always returns empty string.
119+ func ResolveStudioProDir (version string ) string {
120+ if runtime .GOOS != "windows" {
121+ return ""
122+ }
123+ for _ , dir := range windowsProgramDirs () {
124+ candidate := filepath .Join (dir , "Mendix" , version )
125+ if info , err := os .Stat (filepath .Join (candidate , "modeler" , "mxbuild.exe" )); err == nil && ! info .IsDir () {
126+ return candidate
127+ }
128+ }
129+ return ""
130+ }
131+
132+ // windowsProgramDirs returns candidate Program Files directories on Windows,
133+ // derived from environment variables and the system drive letter.
134+ func windowsProgramDirs () []string {
135+ seen := map [string ]bool {}
136+ var dirs []string
137+ add := func (d string ) {
138+ if d != "" && ! seen [d ] {
139+ seen [d ] = true
140+ dirs = append (dirs , d )
141+ }
142+ }
143+ for _ , env := range []string {"PROGRAMFILES" , "PROGRAMW6432" , "PROGRAMFILES(X86)" } {
144+ add (os .Getenv (env ))
145+ }
146+ // Fallback: derive from SystemDrive (e.g., "D:\Program Files").
147+ // SystemDrive returns "D:" without a trailing separator; filepath.Join
148+ // treats "D:" as a relative path, producing "D:Program Files" instead of
149+ // "D:\Program Files". Append the separator explicitly.
150+ if sysDrive := os .Getenv ("SystemDrive" ); sysDrive != "" {
151+ root := sysDrive + string (os .PathSeparator )
152+ add (filepath .Join (root , "Program Files" ))
153+ add (filepath .Join (root , "Program Files (x86)" ))
154+ }
155+ return dirs
156+ }
157+
158+ // mendixSearchPaths returns OS-specific glob patterns for a Mendix binary
159+ // (e.g., "mxbuild.exe", "mx.exe", "mxbuild", "mx") inside Studio Pro installations.
160+ func mendixSearchPaths (binaryName string ) []string {
95161 switch runtime .GOOS {
96162 case "windows" :
97- return []string {
98- `C:\Program Files\Mendix\*\modeler\mxbuild.exe` ,
99- `C:\Program Files (x86)\ Mendix\*\ modeler\mxbuild.exe` ,
163+ var paths []string
164+ for _ , dir := range windowsProgramDirs () {
165+ paths = append ( paths , filepath . Join ( dir , " Mendix" , "*" , " modeler" , binaryName ))
100166 }
167+ return paths
101168 case "darwin" :
102- return []string {
103- "/Applications/Mendix/*/modeler/mxbuild" ,
104- }
169+ return []string {filepath .Join ("/Applications/Mendix/*/modeler" , binaryName )}
105170 default : // linux
106- home , _ := os .UserHomeDir ()
107- paths := []string {
108- "/opt/mendix/*/modeler/mxbuild" ,
109- }
110- if home != "" {
111- paths = append (paths , filepath .Join (home , ".mendix/*/modeler/mxbuild" ))
171+ paths := []string {filepath .Join ("/opt/mendix/*/modeler" , binaryName )}
172+ if home , err := os .UserHomeDir (); err == nil {
173+ paths = append (paths , filepath .Join (home , ".mendix/*/modeler" , binaryName ))
112174 }
113175 return paths
114176 }
115177}
116178
179+ // mxbuildSearchPaths returns OS-specific glob patterns for MxBuild.
180+ func mxbuildSearchPaths () []string {
181+ return mendixSearchPaths (mxbuildBinaryName ())
182+ }
183+
117184// resolveJDK21 finds a JDK 21 installation.
118185// Priority: JAVA_HOME (verify version) > macOS java_home > java in PATH (verify version) > OS-specific known locations.
119186func resolveJDK21 () (string , error ) {
@@ -174,11 +241,15 @@ func resolveMacOSJavaHome() (string, error) {
174241func jdkSearchPaths () []string {
175242 switch runtime .GOOS {
176243 case "windows" :
177- return []string {
178- `C:\Program Files\Eclipse Adoptium\jdk-21*` ,
179- `C:\Program Files\Java\jdk-21*` ,
180- `C:\Program Files\Microsoft\jdk-21*` ,
244+ var paths []string
245+ for _ , dir := range windowsProgramDirs () {
246+ paths = append (paths ,
247+ filepath .Join (dir , "Eclipse Adoptium" , "jdk-21*" ),
248+ filepath .Join (dir , "Java" , "jdk-21*" ),
249+ filepath .Join (dir , "Microsoft" , "jdk-21*" ),
250+ )
181251 }
252+ return paths
182253 case "darwin" :
183254 return []string {
184255 "/Library/Java/JavaVirtualMachines/temurin-21*/Contents/Home" ,
0 commit comments