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,41 +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" :
97163 var paths []string
98- // Use environment variables — the system drive is not always C:.
99- for _ , env := range []string {"PROGRAMFILES" , "PROGRAMW6432" , "PROGRAMFILES(X86)" } {
100- if dir := os .Getenv (env ); dir != "" {
101- paths = append (paths , filepath .Join (dir , "Mendix" , "*" , "modeler" , "mxbuild.exe" ))
102- }
103- }
104- if len (paths ) == 0 {
105- // Fallback if env vars are missing (unlikely but safe).
106- paths = []string {`C:\Program Files\Mendix\*\modeler\mxbuild.exe` }
164+ for _ , dir := range windowsProgramDirs () {
165+ paths = append (paths , filepath .Join (dir , "Mendix" , "*" , "modeler" , binaryName ))
107166 }
108167 return paths
109168 case "darwin" :
110- return []string {
111- "/Applications/Mendix/*/modeler/mxbuild" ,
112- }
169+ return []string {filepath .Join ("/Applications/Mendix/*/modeler" , binaryName )}
113170 default : // linux
114- home , _ := os .UserHomeDir ()
115- paths := []string {
116- "/opt/mendix/*/modeler/mxbuild" ,
117- }
118- if home != "" {
119- 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 ))
120174 }
121175 return paths
122176 }
123177}
124178
179+ // mxbuildSearchPaths returns OS-specific glob patterns for MxBuild.
180+ func mxbuildSearchPaths () []string {
181+ return mendixSearchPaths (mxbuildBinaryName ())
182+ }
183+
125184// resolveJDK21 finds a JDK 21 installation.
126185// Priority: JAVA_HOME (verify version) > macOS java_home > java in PATH (verify version) > OS-specific known locations.
127186func resolveJDK21 () (string , error ) {
@@ -183,17 +242,12 @@ func jdkSearchPaths() []string {
183242 switch runtime .GOOS {
184243 case "windows" :
185244 var paths []string
186- for _ , env := range []string {"PROGRAMFILES" , "PROGRAMW6432" } {
187- if dir := os .Getenv (env ); dir != "" {
188- paths = append (paths ,
189- filepath .Join (dir , "Eclipse Adoptium" , "jdk-21*" ),
190- filepath .Join (dir , "Java" , "jdk-21*" ),
191- filepath .Join (dir , "Microsoft" , "jdk-21*" ),
192- )
193- }
194- }
195- if len (paths ) == 0 {
196- paths = []string {`C:\Program Files\Eclipse Adoptium\jdk-21*` }
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+ )
197251 }
198252 return paths
199253 case "darwin" :
0 commit comments