Manager: allow setup to finish without Blender

Add an option to the setup assistant to skip configuring the path to
Blender. It will just use the `default` option, which causes the Workers
to try and find Blender on their own.

Fixes #104306

Reviewed-on: https://projects.blender.org/studio/flamenco/pulls/104306
Reviewed-by: Sybren A. Stüvel <sybren@blender.org>
This commit is contained in:
Mateus Abelli 2024-09-09 11:10:53 +02:00 committed by Sybren A. Stüvel
parent 0a98fd2b96
commit 6baa132c43
3 changed files with 94 additions and 26 deletions

View File

@ -21,6 +21,14 @@ import (
"projects.blender.org/studio/flamenco/pkg/api" "projects.blender.org/studio/flamenco/pkg/api"
) )
var (
ErrSetupConfigUnusableSource = errors.New("sources should not have the 'is_usable' field set to false")
ErrSetupConfigEmptyStorageLocation = errors.New("'storageLocation' field must not be empty")
ErrSetupConfigEmptyPath = errors.New("'path' field must not be empty while using the 'file_association' source")
ErrSetupConfigEmptyPathOrInput = errors.New("'path' or 'input' fields must not be empty while using the 'input_path' or 'path_envvar' sources")
ErrSetupConfigEmptySource = errors.New("'source' field must not be empty")
)
func (f *Flamenco) GetVersion(e echo.Context) error { func (f *Flamenco) GetVersion(e echo.Context) error {
return e.JSON(http.StatusOK, api.FlamencoVersion{ return e.JSON(http.StatusOK, api.FlamencoVersion{
Version: appinfo.ExtendedVersion(), Version: appinfo.ExtendedVersion(),
@ -265,11 +273,9 @@ func (f *Flamenco) SaveSetupAssistantConfig(e echo.Context) error {
logger = logger.With().Interface("config", setupAssistantCfg).Logger() logger = logger.With().Interface("config", setupAssistantCfg).Logger()
if setupAssistantCfg.StorageLocation == "" || if err := checkSetupAssistantConfig(setupAssistantCfg); err != nil {
!setupAssistantCfg.BlenderExecutable.IsUsable || logger.Error().AnErr("cause", err).Msg("setup assistant: configuration is incomplete")
setupAssistantCfg.BlenderExecutable.Path == "" { return sendAPIError(e, http.StatusBadRequest, "configuration is incomplete: %v", err)
logger.Warn().Msg("setup assistant: configuration is incomplete, unable to accept")
return sendAPIError(e, http.StatusBadRequest, "configuration is incomplete")
} }
conf := f.config.Get() conf := f.config.Get()
@ -277,7 +283,7 @@ func (f *Flamenco) SaveSetupAssistantConfig(e echo.Context) error {
var executable string var executable string
switch setupAssistantCfg.BlenderExecutable.Source { switch setupAssistantCfg.BlenderExecutable.Source {
case api.BlenderPathSourceFileAssociation: case api.BlenderPathSourceFileAssociation, api.BlenderPathSourceDefault:
// The Worker will try to use the file association when the command is set // The Worker will try to use the file association when the command is set
// to the string "blender". // to the string "blender".
executable = "blender" executable = "blender"
@ -336,3 +342,37 @@ func flamencoManagerDir() (string, error) {
func commandNeedsQuoting(cmd string) bool { func commandNeedsQuoting(cmd string) bool {
return strings.ContainsAny(cmd, "\n\t;()") return strings.ContainsAny(cmd, "\n\t;()")
} }
func checkSetupAssistantConfig(config api.SetupAssistantConfig) error {
if config.StorageLocation == "" {
return ErrSetupConfigEmptyStorageLocation
}
if !config.BlenderExecutable.IsUsable {
return ErrSetupConfigUnusableSource
}
switch config.BlenderExecutable.Source {
case api.BlenderPathSourceDefault:
return nil
case api.BlenderPathSourceFileAssociation:
if config.BlenderExecutable.Path == "" {
return ErrSetupConfigEmptyPath
}
case api.BlenderPathSourceInputPath, api.BlenderPathSourcePathEnvvar:
if config.BlenderExecutable.Path == "" ||
config.BlenderExecutable.Input == "" {
return ErrSetupConfigEmptyPathOrInput
}
case "":
return ErrSetupConfigEmptySource
default:
return fmt.Errorf("unknown 'source' field value: %v", config.BlenderExecutable.Source)
}
return nil
}

View File

@ -363,6 +363,27 @@ func TestSaveSetupAssistantConfig(t *testing.T) {
assert.Equal(t, expectBlenderVar, savedConfig.Variables["blender"]) assert.Equal(t, expectBlenderVar, savedConfig.Variables["blender"])
assert.Equal(t, defaultBlenderArgsVar, savedConfig.Variables["blenderArgs"]) assert.Equal(t, defaultBlenderArgsVar, savedConfig.Variables["blenderArgs"])
} }
// Test situation where adding a blender executable was skipped.
{
savedConfig := doTest(api.SetupAssistantConfig{
StorageLocation: mf.tempdir,
BlenderExecutable: api.BlenderPathCheckResult{
IsUsable: true,
Source: api.BlenderPathSourceDefault,
},
})
assert.Equal(t, mf.tempdir, savedConfig.SharedStoragePath)
expectBlenderVar := config.Variable{
Values: config.VariableValues{
{Platform: "linux", Value: "blender"},
{Platform: "windows", Value: "blender"},
{Platform: "darwin", Value: "blender"},
},
}
assert.Equal(t, expectBlenderVar, savedConfig.Variables["blender"])
assert.Equal(t, defaultBlenderArgsVar, savedConfig.Variables["blenderArgs"])
}
} }
func metaTestFixtures(t *testing.T) (mockedFlamenco, func()) { func metaTestFixtures(t *testing.T) (mockedFlamenco, func()) {

View File

@ -114,7 +114,7 @@
<p v-else>Choose how a Worker should invoke the Blender command when performing a task:</p> <p v-else>Choose how a Worker should invoke the Blender command when performing a task:</p>
<fieldset v-if="autoFoundBlenders.length >= 1"> <fieldset>
<label v-if="autoFoundBlenderPathEnvvar" for="blender-path_envvar"> <label v-if="autoFoundBlenderPathEnvvar" for="blender-path_envvar">
<div> <div>
<input <input
@ -191,25 +191,19 @@
</p> </p>
</div> </div>
</label> </label>
</fieldset>
<div v-if="autoFoundBlenders.length === 0"> <label for="blender-default">
<div>
<input <input
v-model="customBlenderExe" type="radio"
@keyup.enter="nextStepAfterCheckBlenderExePath" v-model="selectedBlender"
:class="{ name="blender"
'is-invalid': blenderExeCheckResult != null && !blenderExeCheckResult.is_usable, :value="blenderFromDefaultSource"
}" id="blender-default" />
type="text" {{ sourceLabels['default'] }}
placeholder="Path to Blender executable" />
<p v-if="isBlenderExeChecking" class="is-in-progress">Checking...</p>
<p
v-if="blenderExeCheckResult != null && !blenderExeCheckResult.is_usable"
class="check-failed">
{{ blenderExeCheckResult.cause }}
</p>
</div> </div>
</label>
</fieldset>
</step-item> </step-item>
<step-item <step-item
@ -240,6 +234,9 @@
The command you provided: "<code>{{ selectedBlender.path }}</code The command you provided: "<code>{{ selectedBlender.path }}</code
>" >"
</dd> </dd>
<dd v-if="selectedBlender.source == 'default'">
You have chosen to skip adding a blender path.
</dd>
</dl> </dl>
</div> </div>
<p v-if="isConfirmed" class="check-ok"> <p v-if="isConfirmed" class="check-ok">
@ -291,7 +288,8 @@ export default {
sourceLabels: { sourceLabels: {
file_association: 'Blender that runs when you double-click a .blend file:', file_association: 'Blender that runs when you double-click a .blend file:',
path_envvar: 'Blender found on the $PATH environment:', path_envvar: 'Blender found on the $PATH environment:',
input_path: 'Another Blender executable:', input_path: 'Specify a Blender executable:',
default: 'Skip, let the Workers use whatever Blender is available.',
}, },
isConfirming: false, isConfirming: false,
isConfirmed: false, isConfirmed: false,
@ -324,6 +322,15 @@ export default {
blenderFromInputPath() { blenderFromInputPath() {
return this.allBlenders.find((b) => b.source === 'input_path'); return this.allBlenders.find((b) => b.source === 'input_path');
}, },
blenderFromDefaultSource() {
return {
input: '',
path: '',
source: 'default',
is_usable: true,
cause: '',
};
},
setupConfirmIsClickable() { setupConfirmIsClickable() {
if (this.isConfirming || this.isConfirmed) { if (this.isConfirming || this.isConfirmed) {
return false; return false;