11using System ;
22using System . Collections . Generic ;
3+ using System . Diagnostics . CodeAnalysis ;
34using System . IO ;
45using System . Linq ;
5- using System . Reflection ;
6- using System . Reflection . Emit ;
6+ using System . Text ;
77using System . Windows . Forms ;
88using System . Xml . Linq ;
99using Autodesk . Revit . Attributes ;
1010using Autodesk . Revit . UI ;
11+ using Microsoft . CodeAnalysis ;
1112using RpsRuntime ;
13+ using TaskDialog = Autodesk . Revit . UI . TaskDialog ;
1214
1315namespace RevitPythonShell . RevitCommands
1416{
@@ -23,11 +25,45 @@ namespace RevitPythonShell.RevitCommands
2325 [ Regeneration ( RegenerationOption . Manual ) ]
2426 public class DeployRpsAddinCommand : IExternalCommand
2527 {
28+ private const string FileHeaderTemplate = """
29+ using Autodesk.Revit.Attributes;
30+ using RevitPythonShell.RevitCommands;
31+
32+ #nullable disable
33+ """ ;
34+
35+ private const string ExternalCommandTemplate = """
36+ using Autodesk.Revit.Attributes;
37+ using RevitPythonShell.RevitCommands;
38+
39+ #nullable disable
40+
41+ [Regeneration]
42+ [Transaction]
43+ public class CLASSNAME : RpsExternalCommandBase
44+ {
45+ }
46+ """ ;
47+
48+ private const string ExternalApplicationTemplate = """
49+ using Autodesk.Revit.Attributes;
50+ using RevitPythonShell.RevitCommands;
51+
52+ #nullable disable
53+
54+ [Regeneration]
55+ [Transaction]
56+ public class CLASSNAME : RpsExternalApplicationBase
57+ {
58+ }
59+ """ ;
60+
2661 private string _outputFolder ;
2762 private string _rootFolder ;
2863 private string _addinName ;
2964 private XDocument _doc ;
3065
66+ [ UnconditionalSuppressMessage ( "SingleFile" , "IL3000:Avoid accessing Assembly file path when publishing as a single file" , Justification = "<Pending>" ) ]
3167 Result IExternalCommand . Execute ( ExternalCommandData commandData , ref string message , Autodesk . Revit . DB . ElementSet elements )
3268 {
3369 try
@@ -46,8 +82,8 @@ Result IExternalCommand.Execute(ExternalCommandData commandData, ref string mess
4682 // copy static stuff (rpsaddin runtime, ironpython dlls etc., addin installation utilities)
4783 CopyFile ( typeof ( RpsExternalApplicationBase ) . Assembly . Location ) ; // RpsRuntime.dll
4884
49- var ironPythonPath = Path . GetDirectoryName ( this . GetType ( ) . Assembly . Location ) ;
50- CopyFile ( Path . Combine ( ironPythonPath , "IronPython.dll" ) ) ; // IronPython.dll
85+ var ironPythonPath = Path . GetDirectoryName ( GetType ( ) . Assembly . Location ) ;
86+ CopyFile ( Path . Combine ( ironPythonPath ! , "IronPython.dll" ) ) ; // IronPython.dll
5187 CopyFile ( Path . Combine ( ironPythonPath , "IronPython.Modules.dll" ) ) ; // IronPython.Modules.dll
5288 CopyFile ( Path . Combine ( ironPythonPath , "Microsoft.Scripting.dll" ) ) ; // Microsoft.Scripting.dll
5389 CopyFile ( Path . Combine ( ironPythonPath , "Microsoft.Scripting.Metadata.dll" ) ) ; // Microsoft.Scripting.Metadata.dll
@@ -68,7 +104,7 @@ Result IExternalCommand.Execute(ExternalCommandData commandData, ref string mess
68104 catch ( Exception exception )
69105 {
70106
71- TaskDialog . Show ( "Deploy RpsAddin" , "Error deploying addin: " + exception . ToString ( ) ) ;
107+ TaskDialog . Show ( "Deploy RpsAddin" , $ "Error deploying addin: { exception } " ) ;
72108 return Result . Failed ;
73109 }
74110 }
@@ -84,8 +120,6 @@ Result IExternalCommand.Execute(ExternalCommandData commandData, ref string mess
84120 /// </summary>
85121 private void CopyIcons ( )
86122 {
87- HashSet < string > copiedIcons = new HashSet < string > ( ) ;
88-
89123 foreach ( var pb in _doc . Descendants ( "PushButton" ) )
90124 {
91125 CopyReferencedFileToOutputFolder ( pb . Attribute ( "largeImage" ) ) ;
@@ -108,7 +142,7 @@ private void CopyExplicitFiles()
108142 {
109143 foreach ( var xmlFile in _doc . Descendants ( "Files" ) . SelectMany ( f => f . Descendants ( "File" ) ) )
110144 {
111- var source = xmlFile . Attribute ( "src" ) . Value ;
145+ var source = xmlFile . Attribute ( "src" ) ! . Value ;
112146 var sourcePath = GetRootedPath ( _rootFolder , source ) ;
113147
114148 if ( ! File . Exists ( sourcePath ) )
@@ -122,7 +156,7 @@ private void CopyExplicitFiles()
122156 File . Copy ( sourcePath , Path . Combine ( _outputFolder , fileName ) ) ;
123157
124158 // remove path information for deployment
125- xmlFile . Attribute ( "src" ) . Value = fileName ;
159+ xmlFile . Attribute ( "src" ) ! . Value = fileName ;
126160 }
127161 }
128162
@@ -166,7 +200,7 @@ private string GetAddinXmlPath()
166200 dialog . CheckPathExists = true ;
167201 dialog . Multiselect = false ;
168202 dialog . DefaultExt = "xml" ;
169- dialog . Filter = "RpsAddin xml files (*.xml)|*.xml" ;
203+ dialog . Filter = @ "RpsAddin xml files (*.xml)|*.xml";
170204
171205 dialog . ShowDialog ( ) ;
172206 return dialog . FileName ;
@@ -180,63 +214,76 @@ private string GetAddinXmlPath()
180214 /// </summary>
181215 private void CreateAssembly ( )
182216 {
183- var assemblyName = new AssemblyName { Name = _addinName + ".dll" , Version = new Version ( 1 , 0 , 0 , 0 ) } ; // FIXME: read version from doc
184- var assemblyBuilder = AppDomain . CurrentDomain . DefineDynamicAssembly ( assemblyName , AssemblyBuilderAccess . RunAndSave , _outputFolder ) ;
185- var moduleBuilder = assemblyBuilder . DefineDynamicModule ( "RpsAddinModule" , _addinName + ".dll" ) ;
186-
217+ string dllPath = Path . Combine ( _outputFolder , $ "{ _addinName } .dll") ;
218+
219+ StringBuilder sourceCode = new StringBuilder ( ) ;
220+ sourceCode . Append ( FileHeaderTemplate ) ;
221+ sourceCode . Append ( ExternalApplicationTemplate . Replace ( "CLASSNAME" , _addinName ) ) ;
222+
223+ List < ResourceDescription > resources = new List < ResourceDescription > ( ) ;
224+
187225 foreach ( var xmlPushButton in _doc . Descendants ( "PushButton" ) )
188226 {
189227 string scriptFileName ;
190228 if ( xmlPushButton . Attribute ( "src" ) != null )
191229 {
192- scriptFileName = xmlPushButton . Attribute ( "src" ) . Value ;
230+ scriptFileName = xmlPushButton . Attribute ( "src" ) ! . Value ;
193231 }
194232 else if ( xmlPushButton . Attribute ( "script" ) != null ) // Backwards compatibility
195233 {
196- scriptFileName = xmlPushButton . Attribute ( "script" ) . Value ;
234+ scriptFileName = xmlPushButton . Attribute ( "script" ) ! . Value ;
197235 }
198236 else
199237 {
200238 throw new ApplicationException ( "<PushButton/> tag missing a src attribute in addin manifest" ) ;
201239 }
202240
203- var scriptFile = GetRootedPath ( _rootFolder , scriptFileName ) ; // e.g. "C:\projects\helloworld\helloworld.py" or "..\helloworld.py"
204- var newScriptFile = Path . GetFileName ( scriptFile ) ; // e.g. "helloworld.py" - strip path for embedded resource
205- var className = "ec_" + Path . GetFileNameWithoutExtension ( newScriptFile ) ; // e.g. "ec_helloworld", "ec" stands for ExternalCommand
206-
207- var scriptStream = File . OpenRead ( scriptFile ) ;
208- moduleBuilder . DefineManifestResource ( newScriptFile , scriptStream , ResourceAttributes . Public ) ;
241+ var scriptFilePath = GetRootedPath ( _rootFolder , scriptFileName ) ; // e.g. "C:\projects\helloworld\helloworld.py" or "..\helloworld.py"
242+ var embeddedScriptFileName = Path . GetFileName ( scriptFilePath ) ; // e.g. "helloworld.py" - strip path for embedded resource
243+ var className = "ec_" + Path . GetFileNameWithoutExtension ( embeddedScriptFileName ) ; // e.g. "ec_helloworld", "ec" stands for ExternalCommand
209244
245+ var resourceDescription = new ResourceDescription (
246+ embeddedScriptFileName ,
247+ ( ) => new FileStream ( scriptFilePath , FileMode . Open , FileAccess . Read ) ,
248+ isPublic : true ) ;
249+ resources . Add ( resourceDescription ) ;
250+
210251 // script has new path inside assembly, rename it for the RpsAddin xml file we intend to save as a resource
211- xmlPushButton . Attribute ( "src" ) . Value = newScriptFile ;
212-
213- var typeBuilder = moduleBuilder . DefineType (
214- className ,
215- TypeAttributes . Class | TypeAttributes . Public ,
216- typeof ( RpsExternalCommandBase ) ) ;
252+ xmlPushButton . Attribute ( "src" ) ! . Value = embeddedScriptFileName ;
217253
218- AddRegenerationAttributeToType ( typeBuilder ) ;
219- AddTransactionAttributeToType ( typeBuilder ) ;
220-
221- typeBuilder . CreateType ( ) ;
254+ sourceCode . Append ( ExternalCommandTemplate . Replace ( "CLASSNAME" , className ) ) ;
222255 }
223256
224257 // add StartupScript to addin assembly
225- if ( _doc . Descendants ( "StartupScript" ) . Count ( ) > 0 )
258+ if ( _doc . Descendants ( "StartupScript" ) . Any ( ) )
226259 {
227260 var tag = _doc . Descendants ( "StartupScript" ) . First ( ) ;
228- var scriptFile = GetRootedPath ( _rootFolder , tag . Attribute ( "src" ) . Value ) ;
229- var newScriptFile = Path . GetFileName ( scriptFile ) ;
230- var scriptStream = File . OpenRead ( scriptFile ) ;
231- moduleBuilder . DefineManifestResource ( newScriptFile , scriptStream , ResourceAttributes . Public ) ;
232-
261+ var scriptFilePath = GetRootedPath ( _rootFolder , tag . Attribute ( "src" ) ! . Value ) ;
262+ var embeddedScriptFileName = Path . GetFileName ( scriptFilePath ) ;
263+
264+ var resourceDescription = new ResourceDescription (
265+ embeddedScriptFileName ,
266+ ( ) => new FileStream ( scriptFilePath , FileMode . Open , FileAccess . Read ) ,
267+ isPublic : true ) ;
268+ resources . Add ( resourceDescription ) ;
269+
233270 // script has new path inside assembly, rename it for the RpsAddin xml file we intend to save as a resource
234- tag . Attribute ( "src" ) . Value = newScriptFile ;
271+ tag . Attribute ( "src" ) ! . Value = embeddedScriptFileName ;
235272 }
236273
237- AddRpsAddinXmlToAssembly ( _addinName , _doc , moduleBuilder ) ;
238- AddExternalApplicationToAssembly ( _addinName , moduleBuilder ) ;
239- assemblyBuilder . Save ( _addinName + ".dll" ) ;
274+ resources . Add ( new ResourceDescription ( $ "{ _addinName } .xml", ( ) =>
275+ {
276+ var stream = new MemoryStream ( ) ;
277+ using ( var writer = new StreamWriter ( stream ) )
278+ {
279+ writer . Write ( _doc . ToString ( ) ) ;
280+ writer . Flush ( ) ;
281+ }
282+ stream . Position = 0 ;
283+ return stream ;
284+ } , isPublic : true ) ) ;
285+
286+ DynamicAssemblyCompiler . CompileAndSave ( sourceCode . ToString ( ) , dllPath , resources . ToArray ( ) ) ;
240287 }
241288
242289 /// <summary>
@@ -258,52 +305,6 @@ private static string GetRootedPath(string sourceFolder, string possiblyRelative
258305 return possiblyRelativePath ;
259306 }
260307
261- /// <summary>
262- /// Adds a subclass of RpsExternalApplicationBase to make the assembly
263- /// work as an external application.
264- /// </summary>
265- private void AddExternalApplicationToAssembly ( string addinName , ModuleBuilder moduleBuilder )
266- {
267- var typeBuilder = moduleBuilder . DefineType (
268- addinName ,
269- TypeAttributes . Class | TypeAttributes . Public ,
270- typeof ( RpsExternalApplicationBase ) ) ;
271- AddRegenerationAttributeToType ( typeBuilder ) ;
272- AddTransactionAttributeToType ( typeBuilder ) ;
273- typeBuilder . CreateType ( ) ;
274- }
275-
276- /// <summary>
277- /// Adds the [Transaction(TransactionMode.Manual)] attribute to the type.
278- /// </summary>
279- private void AddTransactionAttributeToType ( TypeBuilder typeBuilder )
280- {
281- var transactionConstructorInfo = typeof ( TransactionAttribute ) . GetConstructor ( new Type [ ] { typeof ( TransactionMode ) } ) ;
282- var transactionAttributeBuilder = new CustomAttributeBuilder ( transactionConstructorInfo , new object [ ] { TransactionMode . Manual } ) ;
283- typeBuilder . SetCustomAttribute ( transactionAttributeBuilder ) ;
284- }
285-
286- /// <summary>
287- /// Adds the [Transaction(TransactionMode.Manual)] attribute to the type.
288- /// </summary>
289- /// <param name="typeBuilder"></param>
290- private void AddRegenerationAttributeToType ( TypeBuilder typeBuilder )
291- {
292- var regenerationConstrutorInfo = typeof ( RegenerationAttribute ) . GetConstructor ( new Type [ ] { typeof ( RegenerationOption ) } ) ;
293- var regenerationAttributeBuilder = new CustomAttributeBuilder ( regenerationConstrutorInfo , new object [ ] { RegenerationOption . Manual } ) ;
294- typeBuilder . SetCustomAttribute ( regenerationAttributeBuilder ) ;
295- }
296-
297- private void AddRpsAddinXmlToAssembly ( string addinName , XDocument doc , ModuleBuilder moduleBuilder )
298- {
299- var stream = new MemoryStream ( ) ;
300- var writer = new StreamWriter ( stream ) ;
301- writer . Write ( doc . ToString ( ) ) ;
302- writer . Flush ( ) ;
303- stream . Position = 0 ;
304- moduleBuilder . DefineManifestResource ( addinName + ".xml" , stream , ResourceAttributes . Public ) ;
305- }
306-
307308 /// <summary>
308309 /// Creates a subfolder in rootFolder with the basename of the
309310 /// RpsAddin xml file and returns the name of that folder.
@@ -314,7 +315,7 @@ private void AddRpsAddinXmlToAssembly(string addinName, XDocument doc, ModuleBui
314315 /// </summary>
315316 private string CreateOutputFolder ( )
316317 {
317- var folderName = string . Format ( "{0}_{1}" , "Output" , _addinName ) ;
318+ var folderName = $ "Output_ { _addinName } " ;
318319 var folderPath = Path . Combine ( _rootFolder , folderName ) ;
319320
320321 if ( Directory . Exists ( folderPath ) )
@@ -323,7 +324,7 @@ private string CreateOutputFolder()
323324 Directory . Delete ( folderPath , true ) ;
324325 }
325326
326- Directory . CreateDirectory ( folderPath , Directory . GetAccessControl ( _rootFolder ) ) ;
327+ Directory . CreateDirectory ( folderPath ) ;
327328 return folderPath ;
328329 }
329330 }
0 commit comments