Skip to content

Commit 6159227

Browse files
authored
Fixes #2404 Route headless PK-Sim-module snapshot loading through PKSim.R.Exchange (#2405)
* Fixes #2404 Route headless PK-Sim-module snapshot loading through PKSim.R.Exchange Separate the PK-Sim snapshot exchange from the rest of the PK-Sim loader so the implementation can be chosen per host: - IPKSimSnapshotConverter (the snapshot Load* methods) + abstract PKSimSnapshotConverterBase holding the shared reflection/serialization logic; the only variation point is the exchange type name. - PKSimStarter (desktop) and the new PKSimSnapshotConverter (MoBi.R, reflecting into PKSim.R.Exchange.SnapshotExchange in the shipped PKSim.R.dll) derive from the base. - Register one implementation per host: PKSimStarter for the desktop app/CLI/batch, PKSimSnapshotConverter for MoBi.R; PKSimStarter is excluded from the shared CoreRegister convention. - ProjectMapper now depends on IPKSimSnapshotConverter; integration tests register the fake under both interfaces. - LoadSimulationsFromSnapshot passes runSimulations: false (load only; the caller runs). - Replace the test snapshot fixture with a clean one (the previous one had a circular formula reference). Depends on PK-Sim Open-Systems-Pharmacology/PK-Sim#3563. * revert snapshot
1 parent 46a29f4 commit 6159227

10 files changed

Lines changed: 173 additions & 88 deletions

File tree

src/MoBi.CLI.Core/ApplicationStartup.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using MoBi.Core.Serialization.Xml;
99
using MoBi.Core.Serialization.Xml.Serializer;
1010
using MoBi.Core.Serialization.Xml.Services;
11+
using MoBi.Core.Services;
1112
using OSPSuite.Core;
1213
using OSPSuite.Core.Domain;
1314
using OSPSuite.Core.Domain.Builder;
@@ -69,6 +70,8 @@ public static void Start()
6970
container.Register<IMoBiXmlSerializerRepository, MoBiCoreXmlSerializerRepository>(LifeStyle.Singleton);
7071
container.Register<ICoreUserSettings, OSPSuite.Core.ICoreUserSettings, CoreUserSettings>(LifeStyle.Singleton);
7172
container.Register<IApplicationSettings, OSPSuite.Core.IApplicationSettings, ApplicationSettings>(LifeStyle.Singleton);
73+
// Desktop CLI uses the installed PK-Sim (PKSim.Starter.dll) for snapshot conversion.
74+
container.Register<IPKSimStarter, IPKSimSnapshotConverter, PKSimStarter>(LifeStyle.Singleton);
7275
container.Register<ISpatialStructureDiagramManager, SpatialStructureDiagramManager>();
7376
container.Register<IMoBiReactionDiagramManager, MoBiReactionDiagramManager>();
7477
container.Register<ISimulationDiagramManager, SimulationDiagramManager>();

src/MoBi.Core/CoreRegister.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ public override void RegisterInContainer(IContainer container)
5151
scan.ExcludeType<GroupRepository>();
5252
scan.ExcludeType<ClipboardManager>();
5353
scan.ExcludeType<ApplicationSettings>();
54+
// The PK-Sim snapshot converter is registered explicitly per host (PKSimStarter for the
55+
// desktop app/CLI, PKSimSnapshotConverter for MoBi.R), so it is not auto-registered here.
56+
scan.ExcludeType<PKSimStarter>();
5457
scan.ExcludeNamespaceContainingType<IMoBiObjectConverter>();
5558
scan.ExcludeNamespaceContainingType<MoBiSimulationDiffBuilder>();
5659
scan.ExcludeNamespaceContainingType<ProjectMapper>();
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using MoBi.Core.Serialization.Xml.Services;
2+
using OSPSuite.Core.Domain;
3+
using OSPSuite.Core.Domain.Builder;
4+
using OSPSuite.Core.Qualification;
5+
6+
namespace MoBi.Core.Services;
7+
8+
public interface IPKSimSnapshotConverter
9+
{
10+
Module LoadModuleFromSnapshot(string serializedSnapshot);
11+
/// <summary>
12+
/// Recreates the PKSim module from the snapshot.
13+
/// If any of the PK-Sim building blocks should have markdown exported it will be done during the module rebuild.
14+
/// </summary>
15+
/// <param name="serializedSnapshot">The PK-Sim project snapshot</param>
16+
/// <param name="qualificationConfiguration">
17+
/// The configuration containing any building block inputs that should have report
18+
/// markdown exported while the module is rebuilt in PK-Sim
19+
/// </param>
20+
/// <returns>A module and any input mappings that were exported</returns>
21+
(Module module, InputMapping[] inputMappings) LoadModuleFromSnapshotAndExportInputs(string serializedSnapshot, QualificationConfiguration qualificationConfiguration);
22+
23+
/// <summary>
24+
/// Recreates a PK-Sim expression profile from the base64 encoded <paramref name="serializedSnapshot" />.
25+
/// </summary>
26+
/// <returns>The expression profile building block, as created in Pk-Sim</returns>
27+
ExpressionProfileBuildingBlock LoadExpressionProfileFromSnapshot(string serializedSnapshot);
28+
29+
/// <summary>
30+
/// Recreates a PK-Sim individual from the base64 encoded <paramref name="serializedSnapshot" />.
31+
/// </summary>
32+
/// <returns>The individual building block, as created in Pk-Sim</returns>
33+
IndividualBuildingBlock LoadIndividualFromSnapshot(string serializedSnapshot);
34+
}
35+
36+
/// <summary>
37+
/// Shared implementation of the PK-Sim snapshot exchange. All exchanges are performed by
38+
/// reflecting into a PK-Sim assembly via <see cref="IPKSimAssemblyLoader" /> and recreating the
39+
/// returned objects in MoBi through serialization. Concrete subclasses differ only in:
40+
/// <list type="bullet">
41+
/// <item>which assembly the loader is pointed at (initialized in their constructor), and</item>
42+
/// <item><see cref="SnapshotExchangeType" /> - the fully-qualified name of the snapshot-exchange
43+
/// type that exposes the <c>Create...</c> methods.</item>
44+
/// </list>
45+
/// The desktop app uses <c>PKSim.Starter.SnapshotExchange</c> (in PKSim.Starter.dll); MoBi.R uses
46+
/// <c>PKSim.R.Exchange.SnapshotExchange</c> (in the shippable PKSim.R.dll).
47+
/// </summary>
48+
public abstract class PKSimSnapshotConverterBase : IPKSimSnapshotConverter
49+
{
50+
protected const string CREATE_MODULE = "CreateModule";
51+
protected const string CREATE_MODULE_AND_EXPORT_INPUTS = "CreateModuleAndExportInputs";
52+
protected const string CREATE_INDIVIDUAL_BUILDING_BLOCK = "CreateIndividualBuildingBlock";
53+
protected const string CREATE_EXPRESSION_PROFILE_BUILDING_BLOCK = "CreateExpressionProfileBuildingBlock";
54+
55+
protected readonly IPKSimAssemblyLoader _pkSimLoader;
56+
private readonly IXmlSerializationService _serializationService;
57+
private readonly IMoBiProjectRetriever _projectRetriever;
58+
59+
protected PKSimSnapshotConverterBase(IPKSimAssemblyLoader pkSimLoader, IXmlSerializationService serializationService, IMoBiProjectRetriever projectRetriever)
60+
{
61+
_pkSimLoader = pkSimLoader;
62+
_serializationService = serializationService;
63+
_projectRetriever = projectRetriever;
64+
}
65+
66+
/// <summary>
67+
/// Fully-qualified name of the PK-Sim type exposing the <c>Create...</c> exchange methods
68+
/// (e.g. <c>PKSim.Starter.SnapshotExchange</c> or <c>PKSim.R.Exchange.SnapshotExchange</c>).
69+
/// </summary>
70+
protected abstract string SnapshotExchangeType { get; }
71+
72+
public Module LoadModuleFromSnapshot(string serializedSnapshot)
73+
{
74+
var element = _pkSimLoader.ExecuteMethod(SnapshotExchangeType, CREATE_MODULE, [serializedSnapshot]) as string;
75+
76+
// all object exchanges should be done using serialization to ensure that objects are recreated in MoBi.
77+
return _serializationService.Deserialize<Module>(element, _projectRetriever.Current);
78+
}
79+
80+
public (Module module, InputMapping[] inputMappings) LoadModuleFromSnapshotAndExportInputs(string serializedSnapshot, QualificationConfiguration qualificationConfiguration)
81+
{
82+
var tuple = _pkSimLoader.ExecuteMethod(SnapshotExchangeType, CREATE_MODULE_AND_EXPORT_INPUTS, [serializedSnapshot, qualificationConfiguration]);
83+
84+
var (element, mappings) = tuple as (string element, InputMapping[] mappings)? ?? (null, null);
85+
86+
// all object exchanges should be done using serialization to ensure that objects are recreated in MoBi.
87+
var module = _serializationService.Deserialize<Module>(element, _projectRetriever.Current);
88+
89+
return (module, mappings);
90+
}
91+
92+
public ExpressionProfileBuildingBlock LoadExpressionProfileFromSnapshot(string serializedSnapshot)
93+
{
94+
var pkml = _pkSimLoader.ExecuteMethod(SnapshotExchangeType, CREATE_EXPRESSION_PROFILE_BUILDING_BLOCK, [serializedSnapshot]) as string;
95+
96+
// all object exchanges should be done using serialization to ensure that objects are recreated in MoBi.
97+
return _serializationService.Deserialize<ExpressionProfileBuildingBlock>(pkml, _projectRetriever.Current);
98+
}
99+
100+
public IndividualBuildingBlock LoadIndividualFromSnapshot(string serializedSnapshot)
101+
{
102+
var pkml = _pkSimLoader.ExecuteMethod(SnapshotExchangeType, CREATE_INDIVIDUAL_BUILDING_BLOCK, [serializedSnapshot]) as string;
103+
104+
// all object exchanges should be done using serialization to ensure that objects are recreated in MoBi.
105+
return _serializationService.Deserialize<IndividualBuildingBlock>(pkml, _projectRetriever.Current);
106+
}
107+
}

src/MoBi.Core/Services/PKSimStarter.cs

Lines changed: 12 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,36 @@
1-
using System.Collections.Generic;
1+
using System.Collections.Generic;
22
using System.IO;
3-
using System.Reflection;
43
using MoBi.Assets;
54
using MoBi.Core.Exceptions;
65
using MoBi.Core.Serialization.Xml.Services;
76
using OSPSuite.Core.Domain.Builder;
87
using OSPSuite.Core.Domain.Services;
9-
using OSPSuite.Core.Qualification;
108
using OSPSuite.Core.Services;
119
using OSPSuite.Utility;
1210
using OSPSuite.Utility.Extensions;
13-
using Module = OSPSuite.Core.Domain.Module;
1411

1512
namespace MoBi.Core.Services
1613
{
17-
public interface IPKSimStarter
14+
public interface IPKSimStarter : IPKSimSnapshotConverter
1815
{
1916
void StartPopulationSimulationWithSimulationFile(string simulationFilePath);
2017
void StartWithWorkingJournalFile(string journalFilePath);
2118
IBuildingBlock CreateProfileExpression(ExpressionType expressionType);
2219
IBuildingBlock CreateIndividual();
2320
IReadOnlyList<ExpressionParameterValueUpdate> UpdateExpressionProfileFromDatabase(ExpressionProfileBuildingBlock expressionProfile);
24-
Module LoadModuleFromSnapshot(string serializedSnapshot);
25-
26-
/// <summary>
27-
/// Recreates the PKSim module from the snapshot.
28-
/// If any of the PK-Sim building blocks should have markdown exported it will be done during the module rebuild.
29-
/// </summary>
30-
/// <param name="serializedSnapshot">The PK-Sim project snapshot</param>
31-
/// <param name="qualificationConfiguration">
32-
/// The configuration containing any building block inputs that should have report
33-
/// markdown exported while the module is rebuilt in PK-Sim
34-
/// </param>
35-
/// <returns>A module and any input mappings that were exported</returns>
36-
(Module module, InputMapping[] inputMappings) LoadModuleFromSnapshotAndExportInputs(string serializedSnapshot, QualificationConfiguration qualificationConfiguration);
37-
38-
/// <summary>
39-
/// Recreates a PK-Sim expression profile from the base64 encoded <paramref name="serializedSnapshot" />.
40-
/// </summary>
41-
/// <returns>The expression profile building block, as created in Pk-Sim</returns>
42-
ExpressionProfileBuildingBlock LoadExpressionProfileFromSnapshot(string serializedSnapshot);
43-
44-
/// <summary>
45-
/// Recreates a PK-Sim individual from the base64 encoded <paramref name="serializedSnapshot" />.
46-
/// </summary>
47-
/// <returns>The individual building block, as created in Pk-Sim</returns>
48-
IndividualBuildingBlock LoadIndividualFromSnapshot(string serializedSnapshot);
4921
}
5022

51-
public class PKSimStarter : IPKSimStarter
23+
/// <summary>
24+
/// Desktop PK-Sim integration. In addition to the snapshot exchange (reflecting into
25+
/// <c>PKSim.Starter.SnapshotExchange</c> in the installed PK-Sim's PKSim.Starter.dll) it can launch
26+
/// the PK-Sim executable and create building blocks through the PK-Sim UI starter.
27+
/// </summary>
28+
public class PKSimStarter : PKSimSnapshotConverterBase, IPKSimStarter
5229
{
5330
private const string CREATE_INDIVIDUAL_ENZYME_EXPRESSION_PROFILE = "CreateIndividualEnzymeExpressionProfile";
5431
private const string CREATE_BINDING_PARTNER_EXPRESSION_PROFILE = "CreateBindingPartnerExpressionProfile";
5532
private const string CREATE_TRANSPORTER_EXPRESSION_PROFILE = "CreateTransporterExpressionProfile";
5633
private const string CREATE_INDIVIDUAL = "CreateIndividual";
57-
private const string CREATE_MODULE = "CreateModule";
58-
private const string CREATE_MODULE_AND_EXPORT_INPUTS = "CreateModuleAndExportInputs";
59-
private const string CREATE_INDIVIDUAL_BUILDING_BLOCK = "CreateIndividualBuildingBlock";
60-
private const string CREATE_EXPRESSION_PROFILE_BUILDING_BLOCK = "CreateExpressionProfileBuildingBlock";
6134
private const string GET_EXPRESSION_DATABASE_QUERY = "GetExpressionDatabaseQuery";
6235
private const string PKSIM_UI_STARTER_EXPRESSION_PROFILE_CREATOR = "PKSim.Starter.ExpressionProfileCreator";
6336
private const string PKSIM_UI_STARTER_INDIVIDUAL_CREATOR = "PKSim.Starter.IndividualCreator";
@@ -68,9 +41,6 @@ public class PKSimStarter : IPKSimStarter
6841
private readonly IApplicationSettings _applicationSettings;
6942
private readonly IStartableProcessFactory _startableProcessFactory;
7043
private readonly ICloneManagerForBuildingBlock _cloneManager;
71-
private readonly IMoBiProjectRetriever _projectRetriever;
72-
private readonly IXmlSerializationService _serializationService;
73-
private readonly IPKSimAssemblyLoader _pkSimLoader;
7444

7545
public PKSimStarter(IMoBiConfiguration configuration,
7646
IApplicationSettings applicationSettings,
@@ -79,17 +49,17 @@ public PKSimStarter(IMoBiConfiguration configuration,
7949
IXmlSerializationService serializationService,
8050
IMoBiProjectRetriever projectRetriever,
8151
IPKSimAssemblyLoader pkSimLoader)
52+
: base(pkSimLoader, serializationService, projectRetriever)
8253
{
8354
_configuration = configuration;
8455
_applicationSettings = applicationSettings;
8556
_startableProcessFactory = startableProcessFactory;
8657
_cloneManager = cloneManager;
87-
_serializationService = serializationService;
88-
_projectRetriever = projectRetriever;
89-
_pkSimLoader = pkSimLoader;
9058
_pkSimLoader.InitializePath(retrievePKSimAssemblyPath());
9159
}
9260

61+
protected override string SnapshotExchangeType => PKSIM_UI_STARTER_SNAPSHOT_EXCHANGE;
62+
9363
public void StartPopulationSimulationWithSimulationFile(string simulationFilePath)
9464
{
9565
startPKSimWithFile(simulationFilePath, AppConstants.PKSim.PopulationSimulationArgument);
@@ -138,42 +108,6 @@ public IReadOnlyList<ExpressionParameterValueUpdate> UpdateExpressionProfileFrom
138108
return _pkSimLoader.ExecuteMethod(PKSIM_UI_STARTER_EXPRESSION_PROFILE_CREATOR, GET_EXPRESSION_DATABASE_QUERY, [expressionProfile]) as List<ExpressionParameterValueUpdate>;
139109
}
140110

141-
public Module LoadModuleFromSnapshot(string serializedSnapshot)
142-
{
143-
var element = _pkSimLoader.ExecuteMethod(PKSIM_UI_STARTER_SNAPSHOT_EXCHANGE, CREATE_MODULE, [serializedSnapshot]) as string;
144-
145-
// all object exchanges should be done using serialization to ensure that objects are recreated in MoBi.
146-
return _serializationService.Deserialize<Module>(element, _projectRetriever.Current);
147-
}
148-
149-
public (Module module, InputMapping[] inputMappings) LoadModuleFromSnapshotAndExportInputs(string serializedSnapshot, QualificationConfiguration qualificationConfiguration)
150-
{
151-
var tuple = _pkSimLoader.ExecuteMethod(PKSIM_UI_STARTER_SNAPSHOT_EXCHANGE, CREATE_MODULE_AND_EXPORT_INPUTS, [serializedSnapshot, qualificationConfiguration]);
152-
153-
var (element, mappings) = tuple as (string element, InputMapping[] mappings)? ?? (null, null);
154-
155-
// all object exchanges should be done using serialization to ensure that objects are recreated in MoBi.
156-
var module = _serializationService.Deserialize<Module>(element, _projectRetriever.Current);
157-
158-
return (module, mappings);
159-
}
160-
161-
public ExpressionProfileBuildingBlock LoadExpressionProfileFromSnapshot(string serializedSnapshot)
162-
{
163-
var pkml = _pkSimLoader.ExecuteMethod(PKSIM_UI_STARTER_SNAPSHOT_EXCHANGE, CREATE_EXPRESSION_PROFILE_BUILDING_BLOCK, [serializedSnapshot]) as string;
164-
165-
// all object exchanges should be done using serialization to ensure that objects are recreated in MoBi.
166-
return _serializationService.Deserialize<ExpressionProfileBuildingBlock>(pkml, _projectRetriever.Current);
167-
}
168-
169-
public IndividualBuildingBlock LoadIndividualFromSnapshot(string serializedSnapshot)
170-
{
171-
var pkml = _pkSimLoader.ExecuteMethod(PKSIM_UI_STARTER_SNAPSHOT_EXCHANGE, CREATE_INDIVIDUAL_BUILDING_BLOCK, [serializedSnapshot]) as string;
172-
173-
// all object exchanges should be done using serialization to ensure that objects are recreated in MoBi.
174-
return _serializationService.Deserialize<IndividualBuildingBlock>(pkml, _projectRetriever.Current);
175-
}
176-
177111
private void startPKSimWithFile(string filePathToStart, string option)
178112
{
179113
var pkSimPath = retrievePKSimExecutablePath();
@@ -198,7 +132,7 @@ private string retrievePKSimExecutablePath()
198132
{
199133
var prioritizedPath = prioritizedPKSimPath();
200134

201-
if(!FileHelper.FileExists(prioritizedPath))
135+
if (!FileHelper.FileExists(prioritizedPath))
202136
throw new MoBiException(AppConstants.PKSim.NotInstalled);
203137

204138
return prioritizedPath;

src/MoBi.Core/Snapshots/Mappers/ProjectMapper.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class ProjectMapper : ProjectMapper<ModelProject, SnapshotProject, Projec
2828
{
2929
private readonly IXmlSerializationService _xmlSerializationService;
3030
private readonly SimulationMapper _simulationMapper;
31-
private readonly IPKSimStarter _pkSimStarter;
31+
private readonly IPKSimSnapshotConverter _pkSimSnapshotConverter;
3232
private readonly ISimulationSettingsFactory _simulationSettingsFactory;
3333
private readonly ICoreSimulationRunner _simulationRunner;
3434
private readonly ICoreUserSettings _userSettings;
@@ -41,15 +41,15 @@ public ProjectMapper(IXmlSerializationService xmlSerializationService,
4141
IOSPSuiteLogger logger,
4242
ParameterIdentificationMapper parameterIdentificationMapper,
4343
SimulationMapper simulationMapper,
44-
IPKSimStarter pkSimStarter,
44+
IPKSimSnapshotConverter pkSimSnapshotConverter,
4545
ISimulationSettingsFactory simulationSettingsFactory,
4646
ICoreSimulationRunner simulationRunner,
4747
ICoreUserSettings userSettings,
4848
IParameterValueUpdateManager parameterValueUpdateManager) : base(creationMetaDataFactory, logger, context, classificationSnapshotTask, parameterIdentificationMapper)
4949
{
5050
_xmlSerializationService = xmlSerializationService;
5151
_simulationMapper = simulationMapper;
52-
_pkSimStarter = pkSimStarter;
52+
_pkSimSnapshotConverter = pkSimSnapshotConverter;
5353
_simulationSettingsFactory = simulationSettingsFactory;
5454
_simulationRunner = simulationRunner;
5555
_userSettings = userSettings;
@@ -154,14 +154,14 @@ private async Task<ModelProject> mapToModel(SnapshotProject projectSnapshot, Pro
154154

155155
private ExpressionProfileBuildingBlock loadExpressionProfileFromSnapshotAndUpdateValues(ExpressionProfileSnapshot expressionProfileSnapshot, ModelProject project)
156156
{
157-
var buildingBlock = _pkSimStarter.LoadExpressionProfileFromSnapshot(pkSimSnapshotToBase64String(expressionProfileSnapshot.PKSimSnapshot));
157+
var buildingBlock = _pkSimSnapshotConverter.LoadExpressionProfileFromSnapshot(pkSimSnapshotToBase64String(expressionProfileSnapshot.PKSimSnapshot));
158158
expressionProfileSnapshot.UpdatedValues?.Each(x => _parameterValueUpdateManager.UpdateParameterValueIn<ExpressionProfileBuildingBlock, ExpressionParameter>(buildingBlock, x, formulaCacheFrom(expressionProfileSnapshot, project)));
159159
return buildingBlock;
160160
}
161161

162162
private IndividualBuildingBlock loadIndividualFromSnapshotAndUpdateValues(IndividualSnapshot individualSnapshot, ModelProject project)
163163
{
164-
var buildingBlock = _pkSimStarter.LoadIndividualFromSnapshot(pkSimSnapshotToBase64String(individualSnapshot.PKSimSnapshot));
164+
var buildingBlock = _pkSimSnapshotConverter.LoadIndividualFromSnapshot(pkSimSnapshotToBase64String(individualSnapshot.PKSimSnapshot));
165165
individualSnapshot.UpdatedValues?.Each(x => _parameterValueUpdateManager.UpdateParameterValueIn<IndividualBuildingBlock, IndividualParameter>(buildingBlock, x, formulaCacheFrom(individualSnapshot, project)));
166166
return buildingBlock;
167167
}
@@ -204,7 +204,7 @@ private void addSimulations(ModelProject project, MoBiSimulation x)
204204

205205
private void loadModulesFromPKSimSnapshot(string snapshot, ModelProject project)
206206
{
207-
var module = _pkSimStarter.LoadModuleFromSnapshot(snapshot);
207+
var module = _pkSimSnapshotConverter.LoadModuleFromSnapshot(snapshot);
208208

209209
loadModuleToProject(project, module);
210210
}
@@ -216,7 +216,7 @@ private static void loadModuleToProject(ModelProject project, Module module)
216216

217217
private InputMapping[] loadModulesAndExportInputsFromPKSimSnapshot(string snapshot, ModelProject project, QualificationConfiguration config)
218218
{
219-
var (module, inputMappings) = _pkSimStarter.LoadModuleFromSnapshotAndExportInputs(snapshot, config);
219+
var (module, inputMappings) = _pkSimSnapshotConverter.LoadModuleFromSnapshotAndExportInputs(snapshot, config);
220220

221221
loadModuleToProject(project, module);
222222

0 commit comments

Comments
 (0)