Skip to content

Commit a32fd51

Browse files
authored
Merge pull request #40 from mastodon-sc/display-install-errors
Show pip install error message to the user
2 parents 10f4120 + 3c5579e commit a32fd51

3 files changed

Lines changed: 120 additions & 24 deletions

File tree

src/main/java/org/mastodon/blender/setup/BlenderSetupUtils.java

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,19 @@
3535
import java.nio.charset.StandardCharsets;
3636
import java.nio.file.Files;
3737
import java.nio.file.Path;
38+
import java.util.Arrays;
39+
import java.util.List;
3840

3941
public class BlenderSetupUtils
4042
{
4143

4244
public static boolean verifyBlenderBinary( Path blenderPath )
4345
{
44-
try {
45-
String output = runPythonWithinBlender( blenderPath,
46-
"print('Blender started from within Mastodon.')" );
47-
return output.contains( "Blender started from within Mastodon." );
46+
try
47+
{
48+
String expectOutput = "Blender started from within Mastodon.";
49+
String python = "print('" + expectOutput + "')";
50+
return runPythonWithinBlender( blenderPath, python, expectOutput );
4851
}
4952
catch ( Throwable e ) {
5053
return false;
@@ -58,32 +61,43 @@ public static void installAddon( Path blenderPath ) throws IOException
5861
Path pythonScript = tmpDir.resolve( "install_addon.py" );
5962
FileUtils.copyURLToFile( BlenderSetupUtils.class.getResource( "/mastodon_blender_view.zip" ), addonZip.toFile() );
6063
FileUtils.copyURLToFile( BlenderSetupUtils.class.getResource( "/blender-scripts/install_addon.py" ), pythonScript.toFile() );
61-
String output = runCommandGetOutput( blenderPath.toString(), //
64+
Result result = runCommand( blenderPath.toString(), //
6265
"--background", //
6366
"--python", pythonScript.toAbsolutePath().toString(), //
6467
"--", addonZip.toAbsolutePath().toString() ); //
65-
if ( !output.contains( "dependencies installed" ) )
66-
throw new RuntimeException( "Installation of the dependencies for the mastodon_blender_view addon failed:\n" + output );
67-
if ( !output.contains( "mastodon blender view addon installed" ) )
68-
throw new RuntimeException( "Installation of the mastodon_blender_view addon failed:\n" + output );
69-
if ( !output.contains( "google RPC code compiled" ))
70-
throw new RuntimeException( "Installation of the mastodon_blender_view addon failed:\n" + output );
68+
69+
if ( result.exitCode != 0 )
70+
throw new RuntimeException( "Installation failed.\n\n" + result );
71+
72+
if ( !result.stdout.contains( "dependencies installed" ) )
73+
throw new RuntimeException( "Installation of the dependencies for the mastodon_blender_view addon failed:\n\n" + result );
74+
75+
if ( !result.stdout.contains( "mastodon blender view addon installed" ) )
76+
throw new RuntimeException( "Installation of the mastodon_blender_view addon failed:\n\n" + result );
77+
78+
if ( !result.stdout.contains( "google RPC code compiled" ) )
79+
throw new RuntimeException( "Installation of the mastodon_blender_view addon failed:\n\n" + result );
80+
7181
Files.delete( pythonScript );
7282
Files.delete( addonZip );
7383
Files.delete( tmpDir );
7484
}
7585

7686
public static void uninstallAddon( Path blenderPath )
7787
{
78-
String python = "import bpy; bpy.ops.preferences.addon_remove(module='mastodon_blender_view')";
79-
String output = runPythonWithinBlender( blenderPath, python );
88+
String python = "import bpy; " + //
89+
"bpy.ops.preferences.addon_remove(module='mastodon_blender_view'); " + //
90+
"print('uninstall completed');";
91+
runPythonWithinBlender( blenderPath, python, "uninstall completed" );
8092
}
8193

8294
public static boolean isMastodonAddonInstalled( Path blenderPath )
8395
{
84-
String python = "import addon_utils; print([module.__name__ for module in addon_utils.modules()])";
85-
String output = runPythonWithinBlender( blenderPath, python );
86-
return output.contains( "'mastodon_blender_view'" );
96+
String python = "import addon_utils; " + //
97+
"print([module.__name__ for module in addon_utils.modules()])";
98+
String expectedOutput = "'mastodon_blender_view'";
99+
boolean success = runPythonWithinBlender( blenderPath, python, expectedOutput );
100+
return success;
87101
}
88102

89103
public static void runAddonTest( Path blenderPath )
@@ -106,24 +120,67 @@ public static void runAddonTest( Path blenderPath )
106120
process.waitFor();
107121
}
108122

109-
private static String runPythonWithinBlender( Path blenderPath, String python )
123+
private static boolean runPythonWithinBlender( Path blenderPath, String python, String expectedOutput )
110124
{
111-
return runCommandGetOutput( blenderPath.toString(), "--background", "--python-expr", python );
125+
Result result = runCommand( blenderPath.toString(), "--background", "--python-expr", python );
126+
return result.exitCode == 0 && result.stdout.contains( expectedOutput );
112127
}
113128

114-
private static String runCommandGetOutput( String... command )
129+
private static Result runCommand( String... command )
115130
{
116131
try
117132
{
118133
Process process = new ProcessBuilder( command ).start();
119-
String output = IOUtils.toString( process.getInputStream(), StandardCharsets.UTF_8 );
120134
process.waitFor();
121-
return output;
135+
String output = IOUtils.toString( process.getInputStream(), StandardCharsets.UTF_8 );
136+
String error = IOUtils.toString( process.getErrorStream(), StandardCharsets.UTF_8 );
137+
int exitCode = process.exitValue();
138+
return new Result( Arrays.asList( command ), output, error, exitCode );
122139
}
123140
catch ( IOException | InterruptedException e )
124141
{
125142
throw new RuntimeException( e );
126143
}
127144
}
128145

146+
public static class Result
147+
{
148+
149+
private final List< String > command;
150+
151+
private final String stdout;
152+
153+
private final String stderr;
154+
155+
private final int exitCode;
156+
157+
public Result( List< String > command, String stdout, String stderr,
158+
int exitCode )
159+
{
160+
this.command = command;
161+
this.stdout = stdout;
162+
this.stderr = stderr;
163+
this.exitCode = exitCode;
164+
}
165+
166+
@Override
167+
public String toString()
168+
{
169+
final StringBuilder s = new StringBuilder();
170+
s.append( "Command:\n " ).append( command.get( 0 ) );
171+
for ( int i = 1; i < command.size(); i++ )
172+
s.append( " " ).append( addQuotes( command.get( i ) ) );
173+
s.append( "\n\n" );
174+
s.append( "Exit Code:\n " ).append( exitCode ).append("\n\n");
175+
s.append( "Command Output:\n\n" ).append( stdout ).append("\n\n");
176+
s.append( "Command Error:\n\n" ).append( stderr ).append("\n\n");
177+
return s.toString();
178+
}
179+
180+
static String addQuotes(String value)
181+
{
182+
boolean simple = value.matches("[a-zA-Z0-9_-]+");
183+
return simple ? value : "'" + value + "'";
184+
}
185+
}
129186
}

src/main/resources/blender-scripts/install_addon.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def get_python_path():
5353

5454
python_path = get_python_path()
5555
packages = {'grpcio', 'bidict', 'grpcio-tools', 'pandas'}
56-
subprocess.check_output([python_path, '-m', 'pip', 'install', *packages])
56+
subprocess.run([python_path, '-m', 'pip', 'install', *packages], check=True)
5757

5858
# test if dependencies are installed
5959

@@ -77,7 +77,8 @@ def get_python_path():
7777

7878
filename_init_py = [m.__file__ for m in addon_utils.modules() if m.__name__ == "mastodon_blender_view"][0]
7979
addon_dir = os.path.dirname(filename_init_py)
80-
subprocess.check_output([python_path, '-m', 'grpc_tools.protoc', '-I.', '--python_out=.', '--grpc_python_out=.', 'mastodon_blender_view/mastodon-blender-view.proto'], cwd=os.path.dirname(addon_dir))
80+
command = [python_path, '-m', 'grpc_tools.protoc', '-I.', '--python_out=.', '--grpc_python_out=.', 'mastodon_blender_view/mastodon-blender-view.proto']
81+
subprocess.run(command, cwd=os.path.dirname(addon_dir), check=True)
8182

8283
try:
8384
from mastodon_blender_view import mastodon_blender_view_pb2

src/test/java/org/mastodon/blender/setup/BlenderSetupUtilsTest.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,19 @@
3434
import java.io.IOException;
3535
import java.nio.file.Path;
3636
import java.nio.file.Paths;
37+
import java.util.Arrays;
3738

39+
import static org.junit.Assert.assertEquals;
3840
import static org.junit.Assert.assertFalse;
3941
import static org.junit.Assert.assertTrue;
4042

41-
@Ignore
4243
public class BlenderSetupUtilsTest
4344
{
4445
// TODO, improve tests
4546

4647
private final Path blenderBinaryPath = Paths.get("/home/arzt/Applications/blender-3.3.1-linux-x64/blender");
4748

49+
@Ignore("Test requires a Blender installation.")
4850
@Test
4951
public void testInstallAddon() throws IOException
5052
{
@@ -54,11 +56,47 @@ public void testInstallAddon() throws IOException
5456
assertTrue( BlenderSetupUtils.isMastodonAddonInstalled( blenderBinaryPath ) );
5557
}
5658

59+
@Ignore("Test requires a Blender installation.")
5760
@Test
5861
public void testAddon()
5962
throws IOException, InterruptedException
6063
{
6164
BlenderSetupUtils.installAddon( blenderBinaryPath );
6265
BlenderSetupUtils.runAddonTest( blenderBinaryPath );
6366
}
67+
68+
@Test
69+
public void testResultToString() {
70+
BlenderSetupUtils.Result result = new BlenderSetupUtils.Result(Arrays.asList("test", "--if", "/works well/"),
71+
"Hello World!",
72+
"ERROR\nString",
73+
42);
74+
String string = result.toString();
75+
String expected = "Command:\n" +
76+
" test --if '/works well/'\n" +
77+
"\n" +
78+
"Exit Code:\n" +
79+
" 42\n" +
80+
"\n" +
81+
"Command Output:\n" +
82+
"\n" +
83+
"Hello World!\n" +
84+
"\n" +
85+
"Command Error:\n" +
86+
"\n" +
87+
"ERROR\n" +
88+
"String\n\n";
89+
assertEquals(expected, string);
90+
}
91+
92+
@Test
93+
public void testAddQuotes()
94+
{
95+
assertEquals("noquotes", BlenderSetupUtils.Result.addQuotes("noquotes") );
96+
assertEquals("--no-quotes", BlenderSetupUtils.Result.addQuotes("--no-quotes") );
97+
assertEquals("'$needs-quotes'", BlenderSetupUtils.Result.addQuotes("$needs-quotes") );
98+
assertEquals("'needs quotes'", BlenderSetupUtils.Result.addQuotes("needs quotes") );
99+
}
64100
}
101+
102+

0 commit comments

Comments
 (0)