1 /** 2 Generator for project files 3 4 Copyright: © 2012-2013 Matthias Dondorff 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Matthias Dondorff 7 */ 8 module dub.generators.generator; 9 10 import dub.compilers.compiler; 11 import dub.generators.build; 12 import dub.generators.visuald; 13 import dub.internal.vibecompat.core.file; 14 import dub.internal.vibecompat.core.log; 15 import dub.internal.vibecompat.inet.path; 16 import dub.package_; 17 import dub.packagemanager; 18 import dub.project; 19 20 import std.array; 21 import std.exception; 22 import std.file; 23 import std.string; 24 25 26 /** 27 Common interface for project generators/builders. 28 */ 29 class ProjectGenerator 30 { 31 struct TargetInfo { 32 Package pack; 33 Package[] packages; 34 string config; 35 BuildSettings buildSettings; 36 string[] dependencies; 37 string[] linkDependencies; 38 } 39 40 protected { 41 Project m_project; 42 } 43 44 this(Project project) 45 { 46 m_project = project; 47 } 48 49 void generate(GeneratorSettings settings) 50 { 51 if (!settings.config.length) settings.config = m_project.getDefaultConfiguration(settings.platform); 52 53 TargetInfo[string] targets; 54 string[string] configs = m_project.getPackageConfigs(settings.platform, settings.config); 55 56 string[] mainfiles; 57 collect(settings, m_project.mainPackage, targets, configs, mainfiles, null); 58 downwardsInheritSettings(m_project.mainPackage.name, targets, targets[m_project.mainPackage.name].buildSettings); 59 auto bs = &targets[m_project.mainPackage.name].buildSettings; 60 if (bs.targetType == TargetType.executable) bs.addSourceFiles(mainfiles); 61 62 generateTargets(settings, targets); 63 } 64 65 abstract void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets); 66 67 private BuildSettings collect(GeneratorSettings settings, Package pack, ref TargetInfo[string] targets, in string[string] configs, ref string[] main_files, string bin_pack) 68 { 69 if (auto pt = pack.name in targets) return pt.buildSettings; 70 71 // determine the actual target type 72 auto shallowbs = pack.getBuildSettings(settings.platform, configs[pack.name]); 73 TargetType tt = shallowbs.targetType; 74 if (pack is m_project.mainPackage) { 75 if (tt == TargetType.autodetect || tt == TargetType.library) tt = TargetType.staticLibrary; 76 } else { 77 if (tt == TargetType.autodetect || tt == TargetType.library) tt = settings.combined ? TargetType.sourceLibrary : TargetType.staticLibrary; 78 else if (tt == TargetType.dynamicLibrary) { 79 logWarn("Dynamic libraries are not yet supported as dependencies - building as static library."); 80 tt = TargetType.staticLibrary; 81 } 82 } 83 if (tt != TargetType.none && tt != TargetType.sourceLibrary && shallowbs.sourceFiles.empty) { 84 logWarn(`Package %s contains no source files. Please add {"targetType": "none"} to it's package description to avoid building it.`, 85 pack.name); 86 tt = TargetType.none; 87 } 88 89 90 shallowbs.targetType = tt; 91 bool generates_binary = tt != TargetType.sourceLibrary && tt != TargetType.none; 92 93 enforce (generates_binary || pack !is m_project.mainPackage, 94 format("Main package must have a binary target type, not %s. Cannot build.", tt)); 95 96 if (tt == TargetType.none) { 97 // ignore any build settings for targetType none (only dependencies will be processed) 98 shallowbs = BuildSettings.init; 99 } 100 101 // start to build up the build settings 102 BuildSettings buildsettings = settings.buildSettings.dup; 103 processVars(buildsettings, pack.path.toNativeString(), shallowbs, true); 104 buildsettings.addVersions("Have_" ~ stripDlangSpecialChars(pack.name)); 105 106 // remove any mainSourceFile from library builds 107 if (buildsettings.targetType != TargetType.executable && buildsettings.mainSourceFile.length) { 108 buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => f != buildsettings.mainSourceFile)().array; 109 main_files ~= buildsettings.mainSourceFile; 110 } 111 112 logDiagnostic("Generate target %s (%s %s %s)", pack.name, buildsettings.targetType, buildsettings.targetPath, buildsettings.targetName); 113 if (generates_binary) 114 targets[pack.name] = TargetInfo(pack, [pack], configs[pack.name], buildsettings, null); 115 116 foreach (depname, depspec; pack.dependencies) { 117 if (!pack.hasDependency(depname, configs[pack.name])) continue; 118 auto dep = m_project.getDependency(depname, depspec.optional); 119 if (!dep) continue; 120 121 auto depbs = collect(settings, dep, targets, configs, main_files, generates_binary ? pack.name : bin_pack); 122 123 if (depbs.targetType != TargetType.sourceLibrary && depbs.targetType != TargetType.none) { 124 // add a reference to the target binary and remove all source files in the dependency build settings 125 depbs.sourceFiles = depbs.sourceFiles.filter!(f => f.isLinkerFile()).array; 126 depbs.importFiles = null; 127 } 128 129 buildsettings.add(depbs); 130 131 auto pt = (generates_binary ? pack.name : bin_pack) in targets; 132 assert(pt !is null); 133 if (auto pdt = depname in targets) { 134 pt.dependencies ~= depname; 135 pt.linkDependencies ~= depname; 136 if (depbs.targetType == TargetType.staticLibrary) 137 pt.linkDependencies = pt.linkDependencies.filter!(d => !pdt.linkDependencies.canFind(d)).array ~ pdt.linkDependencies; 138 } else pt.packages ~= dep; 139 } 140 141 if (generates_binary) { 142 // add build type settings and convert plain DFLAGS to build options 143 m_project.addBuildTypeSettings(buildsettings, settings.platform, settings.buildType); 144 settings.compiler.extractBuildOptions(buildsettings); 145 enforceBuildRequirements(buildsettings); 146 targets[pack.name].buildSettings = buildsettings.dup; 147 } 148 149 return buildsettings; 150 } 151 152 private void downwardsInheritSettings(string target, TargetInfo[string] targets, in BuildSettings root_settings) 153 { 154 auto ti = &targets[target]; 155 ti.buildSettings.addVersions(root_settings.versions); 156 ti.buildSettings.addDebugVersions(root_settings.debugVersions); 157 ti.buildSettings.addOptions(root_settings.options); 158 ti.buildSettings.prependStringImportPaths(root_settings.stringImportPaths); 159 160 foreach (d; ti.dependencies) 161 downwardsInheritSettings(d, targets, root_settings); 162 } 163 } 164 165 166 struct GeneratorSettings { 167 BuildPlatform platform; 168 Compiler compiler; 169 string config; 170 string buildType; 171 BuildSettings buildSettings; 172 173 bool combined; // compile all in one go instead of each dependency separately 174 175 // only used for generator "build" 176 bool run, force, direct, clean, rdmd; 177 string[] runArgs; 178 } 179 180 181 /** 182 Creates a project generator of the given type for the specified project. 183 */ 184 ProjectGenerator createProjectGenerator(string generator_type, Project app, PackageManager mgr) 185 { 186 assert(app !is null && mgr !is null, "Project and package manager needed to create a generator."); 187 188 generator_type = generator_type.toLower(); 189 switch(generator_type) { 190 default: 191 throw new Exception("Unknown project generator: "~generator_type); 192 case "build": 193 logDebug("Creating build generator."); 194 return new BuildGenerator(app, mgr); 195 case "mono-d": 196 throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead."); 197 case "visuald": 198 logDebug("Creating VisualD generator."); 199 return new VisualDGenerator(app, mgr); 200 } 201 } 202 203 204 /** 205 Runs pre-build commands and performs other required setup before project files are generated. 206 */ 207 void prepareGeneration(in BuildSettings buildsettings) 208 { 209 if( buildsettings.preGenerateCommands.length ){ 210 logInfo("Running pre-generate commands..."); 211 runBuildCommands(buildsettings.preGenerateCommands, buildsettings); 212 } 213 } 214 215 /** 216 Runs post-build commands and copies required files to the binary directory. 217 */ 218 void finalizeGeneration(in BuildSettings buildsettings, bool generate_binary) 219 { 220 if (buildsettings.postGenerateCommands.length) { 221 logInfo("Running post-generate commands..."); 222 runBuildCommands(buildsettings.postGenerateCommands, buildsettings); 223 } 224 225 if (generate_binary) { 226 if (!exists(buildsettings.targetPath)) 227 mkdirRecurse(buildsettings.targetPath); 228 229 if (buildsettings.copyFiles.length) { 230 logInfo("Copying files..."); 231 foreach (f; buildsettings.copyFiles) { 232 auto src = Path(f); 233 auto dst = Path(buildsettings.targetPath) ~ Path(f).head; 234 logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); 235 try { 236 copyFile(src, dst, true); 237 } catch logWarn("Failed to copy to %s", dst.toNativeString()); 238 } 239 } 240 } 241 } 242 243 void runBuildCommands(in string[] commands, in BuildSettings build_settings) 244 { 245 import std.process; 246 import dub.internal.utils; 247 248 string[string] env = environment.toAA(); 249 // TODO: do more elaborate things here 250 // TODO: escape/quote individual items appropriately 251 env["DFLAGS"] = join(cast(string[])build_settings.dflags, " "); 252 env["LFLAGS"] = join(cast(string[])build_settings.lflags," "); 253 env["VERSIONS"] = join(cast(string[])build_settings.versions," "); 254 env["LIBS"] = join(cast(string[])build_settings.libs," "); 255 env["IMPORT_PATHS"] = join(cast(string[])build_settings.importPaths," "); 256 env["STRING_IMPORT_PATHS"] = join(cast(string[])build_settings.stringImportPaths," "); 257 runCommands(commands, env); 258 }