static Configuration ParseCommandLine( IEnumerable <string> arguments, List <BuildGroup> buildGroups, Dictionary <string, IProfile> profiles, Dictionary <string, IAnalyzer> analyzers, AssemblyCache assemblyCache ) { var baseConfig = new Configuration(); var commandLineConfig = new Configuration(); IProfile defaultProfile = new Profiles.Default(); var profileAssemblies = new List <string>(); var analyzerAssemblies = new List <string>(); bool[] autoloadProfiles = new bool[] { true }; bool[] autoloadAnalyzers = new bool[] { true }; string[] newDefaultProfile = new string[] { null }; List <string> filenames; { var os = new Mono.Options.OptionSet { { "o=|out=", "Specifies the output directory for generated javascript and manifests.", (path) => commandLineConfig.OutputDirectory = Path.GetFullPath(path) }, { "nac|noautoconfig", "Suppresses automatic loading of same-named .jsilconfig files located next to solutions and/or assemblies.", (b) => commandLineConfig.AutoLoadConfigFiles = b == null }, { "nt|nothreads", "Suppresses use of multiple threads to speed up the translation process.", (b) => commandLineConfig.UseThreads = b == null }, { "sbc|suppressbugcheck", "Suppresses JSIL bug checks that detect bugs in .NET runtimes and standard libraries.", (b) => commandLineConfig.RunBugChecks = b == null }, "Solution Builder options", { "configuration=", "When building one or more solution files, specifies the build configuration to use (like 'Debug').", (v) => commandLineConfig.SolutionBuilder.Configuration = v }, { "platform=", "When building one or more solution files, specifies the build platform to use (like 'x86').", (v) => commandLineConfig.SolutionBuilder.Platform = v }, { "target=", "When building one or more solution files, specifies the build target to use (like 'Build'). The default is 'Build'.", (v) => commandLineConfig.SolutionBuilder.Target = v }, { "logVerbosity=", "When building one or more solution files, specifies the level of log verbosity. Valid options are 'Quiet', 'Minimal', 'Normal', 'Detailed', and 'Diagnostic'.", (v) => commandLineConfig.SolutionBuilder.LogVerbosity = v }, "Assembly options", { "p=|proxy=", "Loads a type proxy assembly to provide type information for the translator.", (name) => commandLineConfig.Assemblies.Proxies.Add(Path.GetFullPath(name)) }, { "i=|ignore=", "Specifies a regular expression pattern for assembly names that should be ignored during the translation process.", (regex) => commandLineConfig.Assemblies.Ignored.Add(regex) }, { "s=|stub=", "Specifies a regular expression pattern for assembly names that should be stubbed during the translation process. " + "Stubbing forces all methods to be externals.", (regex) => commandLineConfig.Assemblies.Stubbed.Add(regex) }, { "nd|nodeps", "Suppresses the automatic loading and translation of assembly dependencies.", (b) => commandLineConfig.IncludeDependencies = b == null }, { "nodefaults", "Suppresses the default list of stubbed assemblies.", (b) => commandLineConfig.ApplyDefaults = b == null }, { "nolocal", "Disables using local proxy types from translated assemblies.", (b) => commandLineConfig.UseLocalProxies = b == null }, { "fv=|frameworkVersion=", "Specifies the version of the .NET framework proxies to use. " + "This ensures that correct type information is provided (as different versions of the framework use different standard libraries). " + "The only accepted value is currently '4.0'. Default: '4.0'", (fv) => commandLineConfig.FrameworkVersion = double.Parse(fv) }, "Profile options", { "nap|noautoloadprofiles", "Disables automatic loading of profile assemblies from the compiler directory.", (b) => autoloadProfiles[0] = (b == null) }, { "pa=|profileAssembly=", "Loads one or more project profiles from the specified profile assembly. Note that this does not force the profiles to be used.", profileAssemblies.Add }, { "dp=|defaultProfile=", "Overrides the default profile to use for projects by specifying the name of the new default profile.", (profileName) => newDefaultProfile[0] = profileName }, "CodeGenerator options", { "os", "Suppresses struct copy elimination.", (b) => commandLineConfig.CodeGenerator.EliminateStructCopies = b == null }, { "ot", "Suppresses temporary local variable elimination.", (b) => commandLineConfig.CodeGenerator.EliminateTemporaries = b == null }, { "oo", "Suppresses simplification of operator expressions and special method calls.", (b) => commandLineConfig.CodeGenerator.SimplifyOperators = b == null }, { "ol", "Suppresses simplification of loop blocks.", (b) => commandLineConfig.CodeGenerator.SimplifyLoops = b == null }, }; filenames = os.Parse(arguments); if (filenames.Count == 0) { var asmName = Assembly.GetExecutingAssembly().GetName(); Console.WriteLine("==== JSILc v{0}.{1}.{2} ====", asmName.Version.Major, asmName.Version.Minor, asmName.Version.Revision); Console.WriteLine("Specify one or more compiled assemblies (dll/exe) to translate them. Symbols will be loaded if they exist in the same directory."); Console.WriteLine("You can also specify Visual Studio solution files (sln) to build them and automatically translate their output(s)."); Console.WriteLine("Specify the path of a .jsilconfig file to load settings from it."); os.WriteOptionDescriptions(Console.Out); return(null); } } { if (autoloadProfiles[0]) { profileAssemblies.AddRange(Directory.GetFiles( GetJSILDirectory(), "JSIL.Profiles.*.dll" )); } if (autoloadAnalyzers[0]) { analyzerAssemblies.AddRange(Directory.GetFiles( GetJSILDirectory(), "JSIL.Analysis.*.dll" )); } foreach (var filename in profileAssemblies) { var fullPath = Path.GetFullPath(filename); try { IProfile profileInstance = CreateExtensionInstance <IProfile>(fullPath); if (profileInstance != null) { profiles.Add(profileInstance.GetType().Name, profileInstance); } } catch (Exception exc) { Console.Error.WriteLine("Warning: Failed to load profile '{0}': {1}", filename, exc); } } foreach (var filename in analyzerAssemblies) { var fullPath = Path.GetFullPath(filename); try { IAnalyzer analyzerInstance = CreateExtensionInstance <IAnalyzer>(fullPath); if (analyzerInstance != null) { analyzers.Add(analyzerInstance.GetType().Name, analyzerInstance); } } catch (Exception exc) { Console.Error.WriteLine("Warning: Failed to load analyzer '{0}': {1}", filename, exc); } } } var commandLineConfigFilenames = (from fn in filenames where Path.GetExtension(fn) == ".jsilconfig" select fn).ToArray(); // Fail early on nonexistent configuration files foreach (var filename in commandLineConfigFilenames) { if (!File.Exists(filename)) { throw new FileNotFoundException(filename); } } commandLineConfig = MergeConfigurations( commandLineConfig, (from fn in commandLineConfigFilenames select LoadConfiguration(fn)).ToArray() ); if (commandLineConfig.ApplyDefaults.GetValueOrDefault(true)) { baseConfig = MergeConfigurations( LoadConfiguration(Path.Combine( GetJSILDirectory(), "defaults.jsilconfig" )), baseConfig ); } foreach (var solution in (from fn in filenames where Path.GetExtension(fn) == ".sln" select fn) ) { var solutionFullPath = Path.GetFullPath(solution); var solutionDir = Path.GetDirectoryName(solutionFullPath); if (solutionDir == null) { Console.Error.WriteLine("// Can't process solution '{0}' - path seems malformed", solution); continue; } // Fail early if a solution file is missing if (!File.Exists(solutionFullPath)) { throw new FileNotFoundException(solutionFullPath); } var solutionConfigPath = Path.Combine( solutionDir, String.Format("{0}.jsilconfig", Path.GetFileName(solutionFullPath)) ); var solutionConfig = File.Exists(solutionConfigPath) ? new Configuration[] { LoadConfiguration(solutionConfigPath) } : new Configuration[] { }; var mergedSolutionConfig = MergeConfigurations(baseConfig, solutionConfig); var config = MergeConfigurations(mergedSolutionConfig, commandLineConfig); var buildStarted = DateTime.UtcNow.Ticks; var buildResult = SolutionBuilder.SolutionBuilder.Build( solutionFullPath, config.SolutionBuilder.Configuration, config.SolutionBuilder.Platform, config.SolutionBuilder.Target ?? "Build", config.SolutionBuilder.LogVerbosity ); var jss = new JavaScriptSerializer { MaxJsonLength = (1024 * 1024) * 64 }; var buildResultJson = jss.Serialize(buildResult); buildResult = jss.Deserialize <SolutionBuilder.BuildResult>(buildResultJson); var buildEnded = DateTime.UtcNow.Ticks; IProfile profile = defaultProfile; foreach (var candidateProfile in profiles.Values) { if (!candidateProfile.IsAppropriateForSolution(buildResult)) { continue; } Console.Error.WriteLine("// Auto-selected the profile '{0}' for this project.", candidateProfile.GetType().Name); profile = candidateProfile; break; } var localVariables = config.ApplyTo(new VariableSet()); localVariables["SolutionDirectory"] = () => solutionDir; // HACK to let you use assemblyname/etc when copying output files. var buildResultAssembly = buildResult.OutputFiles.FirstOrDefault((fn) => Path.GetExtension(fn) == ".exe") ?? buildResult.OutputFiles.FirstOrDefault((fn) => Path.GetExtension(fn) == ".dll"); if (buildResultAssembly != null) { localVariables.SetAssemblyPath(buildResultAssembly); } var processStarted = DateTime.UtcNow.Ticks; profile.ProcessBuildResult( localVariables, profile.GetConfiguration(config), buildResult ); var processEnded = DateTime.UtcNow.Ticks; { var logPath = localVariables.ExpandPath(String.Format( "%outputdirectory%/{0}.buildlog", Path.GetFileName(solution) ), false); if (!Directory.Exists(Path.GetDirectoryName(logPath))) { Directory.CreateDirectory(Path.GetDirectoryName(logPath)); } using (var logWriter = new StreamWriter(logPath, false, Encoding.UTF8)) { logWriter.WriteLine( "Build of solution '{0}' processed {1} task(s) and produced {2} result file(s):", solution, buildResult.AllItemsBuilt.Length, buildResult.OutputFiles.Length ); foreach (var of in buildResult.OutputFiles) { logWriter.WriteLine(of); } logWriter.WriteLine("----"); logWriter.WriteLine("Elapsed build time: {0:0000.0} second(s).", TimeSpan.FromTicks(buildEnded - buildStarted).TotalSeconds); logWriter.WriteLine("Selected profile '{0}' to process results of this build.", profile.GetType().Name); logWriter.WriteLine("Elapsed processing time: {0:0000.0} second(s).", TimeSpan.FromTicks(processEnded - processStarted).TotalSeconds); } } var outputFiles = buildResult.OutputFiles.Concat( (from eo in config.SolutionBuilder.ExtraOutputs let expanded = localVariables.ExpandPath(eo, true) select expanded) ).ToArray(); if (outputFiles.Length > 0) { var sa = new HashSet <string>(); var group = new BuildGroup { BaseConfiguration = mergedSolutionConfig, BaseVariables = localVariables, FilesToBuild = PurgeDuplicateFilesFromBuildGroup(outputFiles, assemblyCache, sa), Profile = profile, }; group.SkippedAssemblies = sa.ToArray(); buildGroups.Add(group); } } var assemblyNames = (from fn in filenames where Path.GetExtension(fn).Contains(",") || Path.GetExtension(fn).Contains(" ") || Path.GetExtension(fn).Contains("=") select fn).ToArray(); var resolver = new Mono.Cecil.DefaultAssemblyResolver(); var metaResolver = new CachingMetadataResolver(resolver); var resolverParameters = new ReaderParameters { AssemblyResolver = resolver, MetadataResolver = metaResolver, ReadSymbols = false, ReadingMode = ReadingMode.Deferred, }; var resolvedAssemblyPaths = (from an in assemblyNames let asm = resolver.Resolve(an, resolverParameters) where asm != null select asm.MainModule.FullyQualifiedName).ToArray(); var mainGroup = (from fn in filenames where (new[] { ".exe", ".dll" }.Contains(Path.GetExtension(fn))) select fn) .Concat(resolvedAssemblyPaths) .ToArray(); if (mainGroup.Length > 0) { var variables = commandLineConfig.ApplyTo(new VariableSet()); // Fail early if any assemblies are missing foreach (var filename in mainGroup) { if (!File.Exists(filename)) { throw new FileNotFoundException(filename); } } buildGroups.Add(new BuildGroup { BaseConfiguration = baseConfig, BaseVariables = variables, FilesToBuild = mainGroup, Profile = defaultProfile }); } return(commandLineConfig); }
static Configuration ParseCommandLine( IEnumerable<string> arguments, List<BuildGroup> buildGroups, Dictionary<string, IProfile> profiles, AssemblyCache assemblyCache ) { var baseConfig = new Configuration(); var commandLineConfig = new Configuration(); IProfile defaultProfile = new Profiles.Default(); var profileAssemblies = new List<string>(); bool[] autoloadProfiles = new bool[] { true }; string[] newDefaultProfile = new string[] { null }; List<string> filenames; { var os = new Mono.Options.OptionSet { {"o=|out=", "Specifies the output directory for generated javascript and manifests.", (path) => commandLineConfig.OutputDirectory = Path.GetFullPath(path) }, {"nac|noautoconfig", "Suppresses automatic loading of same-named .jsilconfig files located next to solutions and/or assemblies.", (b) => commandLineConfig.AutoLoadConfigFiles = b == null }, {"nt|nothreads", "Suppresses use of multiple threads to speed up the translation process.", (b) => commandLineConfig.UseThreads = b == null }, {"sbc|suppressbugcheck", "Suppresses JSIL bug checks that detect bugs in .NET runtimes and standard libraries.", (b) => commandLineConfig.RunBugChecks = b == null }, "Solution Builder options", {"configuration=", "When building one or more solution files, specifies the build configuration to use (like 'Debug').", (v) => commandLineConfig.SolutionBuilder.Configuration = v }, {"platform=", "When building one or more solution files, specifies the build platform to use (like 'x86').", (v) => commandLineConfig.SolutionBuilder.Platform = v }, {"target=", "When building one or more solution files, specifies the build target to use (like 'Build'). The default is 'Build'.", (v) => commandLineConfig.SolutionBuilder.Target = v }, {"logVerbosity=", "When building one or more solution files, specifies the level of log verbosity. Valid options are 'Quiet', 'Minimal', 'Normal', 'Detailed', and 'Diagnostic'.", (v) => commandLineConfig.SolutionBuilder.LogVerbosity = v }, "Assembly options", {"p=|proxy=", "Loads a type proxy assembly to provide type information for the translator.", (name) => commandLineConfig.Assemblies.Proxies.Add(Path.GetFullPath(name)) }, {"i=|ignore=", "Specifies a regular expression pattern for assembly names that should be ignored during the translation process.", (regex) => commandLineConfig.Assemblies.Ignored.Add(regex) }, {"s=|stub=", "Specifies a regular expression pattern for assembly names that should be stubbed during the translation process. " + "Stubbing forces all methods to be externals.", (regex) => commandLineConfig.Assemblies.Stubbed.Add(regex) }, {"nd|nodeps", "Suppresses the automatic loading and translation of assembly dependencies.", (b) => commandLineConfig.IncludeDependencies = b == null}, {"nodefaults", "Suppresses the default list of stubbed assemblies.", (b) => commandLineConfig.ApplyDefaults = b == null}, {"nolocal", "Disables using local proxy types from translated assemblies.", (b) => commandLineConfig.UseLocalProxies = b == null}, {"fv=|frameworkVersion=", "Specifies the version of the .NET framework proxies to use. " + "This ensures that correct type information is provided (as different versions of the framework use different standard libraries). " + "The only accepted value is currently '4.0'. Default: '4.0'", (fv) => commandLineConfig.FrameworkVersion = double.Parse(fv)}, "Profile options", {"nap|noautoloadprofiles", "Disables automatic loading of profile assemblies from the compiler directory.", (b) => autoloadProfiles[0] = (b == null)}, {"pa=|profileAssembly=", "Loads one or more project profiles from the specified profile assembly. Note that this does not force the profiles to be used.", profileAssemblies.Add}, {"dp=|defaultProfile=", "Overrides the default profile to use for projects by specifying the name of the new default profile.", (profileName) => newDefaultProfile[0] = profileName}, "CodeGenerator options", {"os", "Suppresses struct copy elimination.", (b) => commandLineConfig.CodeGenerator.EliminateStructCopies = b == null}, {"ot", "Suppresses temporary local variable elimination.", (b) => commandLineConfig.CodeGenerator.EliminateTemporaries = b == null}, {"oo", "Suppresses simplification of operator expressions and special method calls.", (b) => commandLineConfig.CodeGenerator.SimplifyOperators = b == null}, {"ol", "Suppresses simplification of loop blocks.", (b) => commandLineConfig.CodeGenerator.SimplifyLoops = b == null}, }; filenames = os.Parse(arguments); if (filenames.Count == 0) { var asmName = Assembly.GetExecutingAssembly().GetName(); Console.WriteLine("==== JSILc v{0}.{1}.{2} ====", asmName.Version.Major, asmName.Version.Minor, asmName.Version.Revision); Console.WriteLine("Specify one or more compiled assemblies (dll/exe) to translate them. Symbols will be loaded if they exist in the same directory."); Console.WriteLine("You can also specify Visual Studio solution files (sln) to build them and automatically translate their output(s)."); Console.WriteLine("Specify the path of a .jsilconfig file to load settings from it."); os.WriteOptionDescriptions(Console.Out); return null; } } { if (autoloadProfiles[0]) profileAssemblies.AddRange(Directory.GetFiles( GetJSILDirectory(), "JSIL.Profiles.*.dll" )); foreach (var filename in profileAssemblies) { var fullPath = Path.GetFullPath(filename); try { var assembly = Assembly.LoadFile(fullPath); foreach (var type in assembly.GetTypes()) { if ( type.FindInterfaces( (interfaceType, o) => interfaceType == (Type)o, typeof(IProfile) ).Length != 1 ) continue; var ctor = type.GetConstructor( BindingFlags.Public | BindingFlags.Instance, null, System.Type.EmptyTypes, null ); var profileInstance = (IProfile)ctor.Invoke(new object[0]); profiles.Add(type.Name, profileInstance); } } catch (Exception exc) { Console.Error.WriteLine("Warning: Failed to load profile '{0}': {1}", filename, exc); } } } var commandLineConfigFilenames = (from fn in filenames where Path.GetExtension(fn) == ".jsilconfig" select fn).ToArray(); // Fail early on nonexistent configuration files foreach (var filename in commandLineConfigFilenames) if (!File.Exists(filename)) throw new FileNotFoundException(filename); commandLineConfig = MergeConfigurations( commandLineConfig, (from fn in commandLineConfigFilenames select LoadConfiguration(fn)).ToArray() ); if (commandLineConfig.ApplyDefaults.GetValueOrDefault(true)) { baseConfig = MergeConfigurations( LoadConfiguration(Path.Combine( GetJSILDirectory(), "defaults.jsilconfig" )), baseConfig ); } foreach (var solution in (from fn in filenames where Path.GetExtension(fn) == ".sln" select fn) ) { var solutionFullPath = Path.GetFullPath(solution); var solutionDir = Path.GetDirectoryName(solutionFullPath); if (solutionDir == null) { Console.Error.WriteLine("// Can't process solution '{0}' - path seems malformed", solution); continue; } // Fail early if a solution file is missing if (!File.Exists(solutionFullPath)) throw new FileNotFoundException(solutionFullPath); var solutionConfigPath = Path.Combine( solutionDir, String.Format("{0}.jsilconfig", Path.GetFileName(solutionFullPath)) ); var solutionConfig = File.Exists(solutionConfigPath) ? new Configuration[] { LoadConfiguration(solutionConfigPath) } : new Configuration[] { }; var mergedSolutionConfig = MergeConfigurations(baseConfig, solutionConfig); var config = MergeConfigurations(mergedSolutionConfig, commandLineConfig); var buildStarted = DateTime.UtcNow.Ticks; var buildResult = SolutionBuilder.SolutionBuilder.Build( solutionFullPath, config.SolutionBuilder.Configuration, config.SolutionBuilder.Platform, config.SolutionBuilder.Target ?? "Build", config.SolutionBuilder.LogVerbosity ); var jss = new JavaScriptSerializer { MaxJsonLength = (1024 * 1024) * 64 }; var buildResultJson = jss.Serialize(buildResult); buildResult = jss.Deserialize<SolutionBuilder.BuildResult>(buildResultJson); var buildEnded = DateTime.UtcNow.Ticks; IProfile profile = defaultProfile; foreach (var candidateProfile in profiles.Values) { if (!candidateProfile.IsAppropriateForSolution(buildResult)) continue; Console.Error.WriteLine("// Auto-selected the profile '{0}' for this project.", candidateProfile.GetType().Name); profile = candidateProfile; break; } var localVariables = config.ApplyTo(new VariableSet()); localVariables["SolutionDirectory"] = () => solutionDir; // HACK to let you use assemblyname/etc when copying output files. var buildResultAssembly = buildResult.OutputFiles.FirstOrDefault((fn) => Path.GetExtension(fn) == ".exe") ?? buildResult.OutputFiles.FirstOrDefault((fn) => Path.GetExtension(fn) == ".dll"); if (buildResultAssembly != null) { localVariables.SetAssemblyPath(buildResultAssembly); } var processStarted = DateTime.UtcNow.Ticks; profile.ProcessBuildResult( localVariables, profile.GetConfiguration(config), buildResult ); var processEnded = DateTime.UtcNow.Ticks; { var logPath = localVariables.ExpandPath(String.Format( "%outputdirectory%/{0}.buildlog", Path.GetFileName(solution) ), false); if (!Directory.Exists(Path.GetDirectoryName(logPath))) Directory.CreateDirectory(Path.GetDirectoryName(logPath)); using (var logWriter = new StreamWriter(logPath, false, Encoding.UTF8)) { logWriter.WriteLine( "Build of solution '{0}' processed {1} task(s) and produced {2} result file(s):", solution, buildResult.AllItemsBuilt.Length, buildResult.OutputFiles.Length ); foreach (var of in buildResult.OutputFiles) logWriter.WriteLine(of); logWriter.WriteLine("----"); logWriter.WriteLine("Elapsed build time: {0:0000.0} second(s).", TimeSpan.FromTicks(buildEnded - buildStarted).TotalSeconds); logWriter.WriteLine("Selected profile '{0}' to process results of this build.", profile.GetType().Name); logWriter.WriteLine("Elapsed processing time: {0:0000.0} second(s).", TimeSpan.FromTicks(processEnded - processStarted).TotalSeconds); } } var outputFiles = buildResult.OutputFiles.Concat( (from eo in config.SolutionBuilder.ExtraOutputs let expanded = localVariables.ExpandPath(eo, true) select expanded) ).ToArray(); if (outputFiles.Length > 0) { var sa = new HashSet<string>(); var group = new BuildGroup { BaseConfiguration = mergedSolutionConfig, BaseVariables = localVariables, FilesToBuild = PurgeDuplicateFilesFromBuildGroup(outputFiles, assemblyCache, sa), Profile = profile, }; group.SkippedAssemblies = sa.ToArray(); buildGroups.Add(group); } } var assemblyNames = (from fn in filenames where Path.GetExtension(fn).Contains(",") || Path.GetExtension(fn).Contains(" ") || Path.GetExtension(fn).Contains("=") select fn).ToArray(); var resolver = new Mono.Cecil.DefaultAssemblyResolver(); var metaResolver = new CachingMetadataResolver(resolver); var resolverParameters = new ReaderParameters { AssemblyResolver = resolver, MetadataResolver = metaResolver, ReadSymbols = false, ReadingMode = ReadingMode.Deferred, }; var resolvedAssemblyPaths = (from an in assemblyNames let asm = resolver.Resolve(an, resolverParameters) where asm != null select asm.MainModule.FullyQualifiedName).ToArray(); var mainGroup = (from fn in filenames where (new[] { ".exe", ".dll" }.Contains(Path.GetExtension(fn))) select fn) .Concat(resolvedAssemblyPaths) .ToArray(); if (mainGroup.Length > 0) { var variables = commandLineConfig.ApplyTo(new VariableSet()); // Fail early if any assemblies are missing foreach (var filename in mainGroup) { if (!File.Exists(filename)) throw new FileNotFoundException(filename); } buildGroups.Add(new BuildGroup { BaseConfiguration = baseConfig, BaseVariables = variables, FilesToBuild = mainGroup, Profile = defaultProfile }); } return commandLineConfig; }