@@ -2,7 +2,7 @@ use crate::dotnet::project::ProjectType;
2
2
use crate :: dotnet:: solution:: Solution ;
3
3
use crate :: Project ;
4
4
use libcnb:: data:: launch:: { Process , ProcessBuilder , ProcessType } ;
5
- use std:: path:: PathBuf ;
5
+ use std:: path:: { Path , PathBuf } ;
6
6
7
7
/// Detects processes in a solution's projects
8
8
pub ( crate ) fn detect_solution_processes ( solution : & Solution ) -> Vec < Process > {
@@ -23,23 +23,36 @@ fn project_launch_process(solution: &Solution, project: &Project) -> Option<Proc
23
23
}
24
24
let relative_executable_path = relative_executable_path ( solution, project) ;
25
25
26
+ let command = build_command ( & relative_executable_path, project. project_type ) ;
27
+
28
+ Some ( ProcessBuilder :: new ( project_process_type ( project) , [ "bash" , "-c" , & command] ) . build ( ) )
29
+ }
30
+
31
+ /// Constructs the shell command for launching the process
32
+ fn build_command ( relative_executable_path : & Path , project_type : ProjectType ) -> String {
33
+ let parent_dir = relative_executable_path
34
+ . parent ( )
35
+ . expect ( "Executable path should always have a parent directory" )
36
+ . to_str ( )
37
+ . expect ( "Path should be valid UTF-8" ) ;
38
+
39
+ let file_name = relative_executable_path
40
+ . file_name ( )
41
+ . expect ( "Executable path should always have a file name" )
42
+ . to_str ( )
43
+ . expect ( "Path should be valid UTF-8" ) ;
44
+
26
45
let mut command = format ! (
27
46
"cd {}; ./{}" ,
28
- relative_executable_path
29
- . parent( )
30
- . expect( "Path to always have a parent directory" )
31
- . display( ) ,
32
- relative_executable_path
33
- . file_name( )
34
- . expect( "Path to never terminate in `..`" )
35
- . to_string_lossy( )
47
+ shell_words:: quote( parent_dir) ,
48
+ shell_words:: quote( file_name)
36
49
) ;
37
50
38
- if project . project_type == ProjectType :: WebApplication {
51
+ if project_type == ProjectType :: WebApplication {
39
52
command. push_str ( " --urls http://*:$PORT" ) ;
40
53
}
41
54
42
- Some ( ProcessBuilder :: new ( project_process_type ( project ) , [ "bash" , "-c" , & command] ) . build ( ) )
55
+ command
43
56
}
44
57
45
58
/// Returns a sanitized process type name, ensuring it is always valid
@@ -56,9 +69,9 @@ fn relative_executable_path(solution: &Solution, project: &Project) -> PathBuf {
56
69
solution
57
70
. path
58
71
. parent ( )
59
- . expect ( "Solution path to have a parent " ) ,
72
+ . expect ( "Solution file should be in a directory " ) ,
60
73
)
61
- . expect ( "Project to be nested in solution parent directory" )
74
+ . expect ( "Executable path should inside the solution's directory" )
62
75
. to_path_buf ( )
63
76
}
64
77
@@ -88,16 +101,24 @@ mod tests {
88
101
use libcnb:: data:: process_type;
89
102
use std:: path:: PathBuf ;
90
103
104
+ fn create_test_project ( path : & str , assembly_name : & str , project_type : ProjectType ) -> Project {
105
+ Project {
106
+ path : PathBuf :: from ( path) ,
107
+ target_framework : "net9.0" . to_string ( ) ,
108
+ project_type,
109
+ assembly_name : assembly_name. to_string ( ) ,
110
+ }
111
+ }
112
+
91
113
#[ test]
92
114
fn test_detect_solution_processes_web_app ( ) {
93
115
let solution = Solution {
94
116
path : PathBuf :: from ( "/tmp/foo.sln" ) ,
95
- projects : vec ! [ Project {
96
- path: PathBuf :: from( "/tmp/bar/bar.csproj" ) ,
97
- target_framework: "net9.0" . to_string( ) ,
98
- project_type: ProjectType :: WebApplication ,
99
- assembly_name: "bar" . to_string( ) ,
100
- } ] ,
117
+ projects : vec ! [ create_test_project(
118
+ "/tmp/bar/bar.csproj" ,
119
+ "bar" ,
120
+ ProjectType :: WebApplication ,
121
+ ) ] ,
101
122
} ;
102
123
103
124
let expected_processes = vec ! [ Process {
@@ -116,23 +137,22 @@ mod tests {
116
137
}
117
138
118
139
#[ test]
119
- fn test_detect_solution_processes_console_app ( ) {
140
+ fn test_detect_solution_processes_with_spaces ( ) {
120
141
let solution = Solution {
121
- path : PathBuf :: from ( "/tmp/foo.sln" ) ,
122
- projects : vec ! [ Project {
123
- path: PathBuf :: from( "/tmp/bar/bar.csproj" ) ,
124
- target_framework: "net9.0" . to_string( ) ,
125
- project_type: ProjectType :: ConsoleApplication ,
126
- assembly_name: "bar" . to_string( ) ,
127
- } ] ,
142
+ path : PathBuf :: from ( "/tmp/My Solution With Spaces.sln" ) ,
143
+ projects : vec ! [ create_test_project(
144
+ "/tmp/My Project With Spaces/project.csproj" ,
145
+ "My App" ,
146
+ ProjectType :: ConsoleApplication ,
147
+ ) ] ,
128
148
} ;
129
149
130
150
let expected_processes = vec ! [ Process {
131
- r#type: process_type!( "bar " ) ,
151
+ r#type: process_type!( "MyApp " ) ,
132
152
command: vec![
133
153
"bash" . to_string( ) ,
134
154
"-c" . to_string( ) ,
135
- "cd bar /bin/publish; ./bar " . to_string( ) ,
155
+ "cd 'My Project With Spaces /bin/publish' ; ./'My App' " . to_string( ) ,
136
156
] ,
137
157
args: vec![ ] ,
138
158
default : false ,
@@ -143,28 +163,31 @@ mod tests {
143
163
}
144
164
145
165
#[ test]
146
- fn test_project_launch_process_non_executable ( ) {
166
+ fn test_relative_executable_path ( ) {
147
167
let solution = Solution {
148
- path : PathBuf :: from ( "/tmp/foo.sln" ) ,
149
- projects : vec ! [ Project {
150
- path: PathBuf :: from( "/tmp/bar/bar.csproj" ) ,
151
- target_framework: "net9.0" . to_string( ) ,
152
- project_type: ProjectType :: Unknown ,
153
- assembly_name: "bar" . to_string( ) ,
154
- } ] ,
168
+ path : PathBuf :: from ( "/tmp/solution.sln" ) ,
169
+ projects : vec ! [ ] ,
155
170
} ;
156
171
157
- assert ! ( detect_solution_processes( & solution) . is_empty( ) ) ;
172
+ let project = create_test_project (
173
+ "/tmp/project/project.csproj" ,
174
+ "TestApp" ,
175
+ ProjectType :: ConsoleApplication ,
176
+ ) ;
177
+
178
+ assert_eq ! (
179
+ relative_executable_path( & solution, & project) ,
180
+ PathBuf :: from( "project/bin/publish/TestApp" )
181
+ ) ;
158
182
}
159
183
160
184
#[ test]
161
185
fn test_project_executable_path ( ) {
162
- let project = Project {
163
- path : PathBuf :: from ( "/tmp/project/project.csproj" ) ,
164
- target_framework : "net9.0" . to_string ( ) ,
165
- project_type : ProjectType :: ConsoleApplication ,
166
- assembly_name : "TestApp" . to_string ( ) ,
167
- } ;
186
+ let project = create_test_project (
187
+ "/tmp/project/project.csproj" ,
188
+ "TestApp" ,
189
+ ProjectType :: ConsoleApplication ,
190
+ ) ;
168
191
169
192
assert_eq ! (
170
193
project_executable_path( & project) ,
@@ -173,22 +196,28 @@ mod tests {
173
196
}
174
197
175
198
#[ test]
176
- fn test_relative_executable_path ( ) {
177
- let solution = Solution {
178
- path : PathBuf :: from ( "/tmp/solution.sln" ) ,
179
- projects : vec ! [ ] ,
180
- } ;
199
+ fn test_build_command_with_spaces ( ) {
200
+ let executable_path = PathBuf :: from ( "some/project with spaces/bin/publish/My App" ) ;
181
201
182
- let project = Project {
183
- path : PathBuf :: from ( "/tmp/project/project.csproj" ) ,
184
- target_framework : "net9.0" . to_string ( ) ,
185
- project_type : ProjectType :: ConsoleApplication ,
186
- assembly_name : "TestApp" . to_string ( ) ,
187
- } ;
202
+ assert_eq ! (
203
+ build_command( & executable_path, ProjectType :: ConsoleApplication ) ,
204
+ "cd 'some/project with spaces/bin/publish'; ./'My App'"
205
+ ) ;
188
206
189
207
assert_eq ! (
190
- relative_executable_path( & solution, & project) ,
191
- PathBuf :: from( "project/bin/publish/TestApp" )
208
+ build_command( & executable_path, ProjectType :: WebApplication ) ,
209
+ "cd 'some/project with spaces/bin/publish'; ./'My App' --urls http://*:$PORT"
210
+ ) ;
211
+ }
212
+
213
+ #[ test]
214
+ fn test_build_command_with_special_chars ( ) {
215
+ let executable_path =
216
+ PathBuf :: from ( "some/project with #special$chars/bin/publish/My-App+v1.2_Release!" ) ;
217
+
218
+ assert_eq ! (
219
+ build_command( & executable_path, ProjectType :: ConsoleApplication ) ,
220
+ "cd 'some/project with #special$chars/bin/publish'; ./My-App+v1.2_Release!"
192
221
) ;
193
222
}
194
223
0 commit comments