1 /** 2 Generator for direct compiler builds. 3 4 Copyright: © 2013-2013 rejectedsoftware e.K. 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module dub.generators.build; 9 10 import dub.compilers.compiler; 11 import dub.generators.generator; 12 import dub.internal.std.process; 13 import dub.internal.utils; 14 import dub.internal.vibecompat.core.file; 15 import dub.internal.vibecompat.core.log; 16 import dub.internal.vibecompat.inet.path; 17 import dub.package_; 18 import dub.packagemanager; 19 import dub.project; 20 21 import std.algorithm; 22 import std.array; 23 import std.conv; 24 import std.exception; 25 import std.file; 26 import std.string; 27 28 29 class BuildGenerator : ProjectGenerator { 30 private { 31 Project m_project; 32 PackageManager m_pkgMgr; 33 } 34 35 this(Project app, PackageManager mgr) 36 { 37 m_project = app; 38 m_pkgMgr = mgr; 39 } 40 41 void generateProject(GeneratorSettings settings) 42 { 43 auto cwd = Path(getcwd()); 44 45 auto buildsettings = settings.buildSettings; 46 m_project.addBuildSettings(buildsettings, settings.platform, settings.config); 47 m_project.addBuildTypeSettings(buildsettings, settings.platform, settings.buildType); 48 49 auto generate_binary = !(buildsettings.options & BuildOptions.syntaxOnly); 50 auto is_static_library = buildsettings.targetType == TargetType.staticLibrary || buildsettings.targetType == TargetType.library; 51 52 // make file paths relative to shrink the command line 53 foreach(ref f; buildsettings.sourceFiles){ 54 auto fp = Path(f); 55 if( fp.absolute ) fp = fp.relativeTo(Path(getcwd())); 56 f = fp.toNativeString(); 57 } 58 59 // find the temp directory 60 auto tmp = getTempDir(); 61 62 if( settings.config.length ) logInfo("Building configuration \""~settings.config~"\", build type "~settings.buildType); 63 else logInfo("Building default configuration, build type "~settings.buildType); 64 65 prepareGeneration(buildsettings); 66 67 // determine the absolute target path 68 if( !Path(buildsettings.targetPath).absolute ) 69 buildsettings.targetPath = (m_project.mainPackage.path ~ Path(buildsettings.targetPath)).toNativeString(); 70 71 // make all target/import paths relative 72 string makeRelative(string path) { auto p = Path(path); if (p.absolute) p = p.relativeTo(cwd); return p.toNativeString(); } 73 buildsettings.targetPath = makeRelative(buildsettings.targetPath); 74 foreach (ref p; buildsettings.importPaths) p = makeRelative(p); 75 foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p); 76 77 Path exe_file_path; 78 if( generate_binary ){ 79 if( settings.run ){ 80 import std.random; 81 auto rnd = to!string(uniform(uint.min, uint.max)); 82 buildsettings.targetPath = (tmp~"dub/"~rnd).toNativeString(); 83 } 84 exe_file_path = Path(buildsettings.targetPath) ~ getTargetFileName(buildsettings, settings.platform); 85 } 86 logDiagnostic("Application output name is '%s'", exe_file_path.toNativeString()); 87 88 finalizeGeneration(buildsettings, generate_binary); 89 90 if( buildsettings.preBuildCommands.length ){ 91 logInfo("Running pre-build commands..."); 92 runBuildCommands(buildsettings.preBuildCommands, buildsettings); 93 } 94 95 // assure that we clean up after ourselves 96 Path[] cleanup_files; 97 scope (exit) { 98 foreach (f; cleanup_files) 99 if (existsFile(f)) 100 remove(f.toNativeString()); 101 if (generate_binary && settings.run) 102 rmdirRecurse(buildsettings.targetPath); 103 } 104 105 /* 106 NOTE: for DMD experimental separate compile/link is used, but this is not yet implemented 107 on the other compilers. Later this should be integrated somehow in the build process 108 (either in the package.json, or using a command line flag) 109 */ 110 if (settings.platform.compilerBinary != "dmd" || !generate_binary || is_static_library) { 111 // setup for command line 112 if( generate_binary ) settings.compiler.setTarget(buildsettings, settings.platform); 113 settings.compiler.prepareBuildSettings(buildsettings, BuildSetting.commandLine); 114 115 // invoke the compiler 116 logInfo("Running %s...", settings.platform.compilerBinary); 117 if( settings.run ) cleanup_files ~= exe_file_path; 118 settings.compiler.invoke(buildsettings, settings.platform); 119 } else { 120 // determine path for the temporary object file 121 version(Windows) enum tempobjname = "temp.obj"; 122 else enum tempobjname = "temp.o"; 123 Path tempobj = Path(buildsettings.targetPath) ~ tempobjname; 124 125 // setup linker command line 126 auto lbuildsettings = buildsettings; 127 lbuildsettings.sourceFiles = lbuildsettings.sourceFiles.filter!(f => f.endsWith(".lib"))().array(); 128 settings.compiler.prepareBuildSettings(lbuildsettings, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles); 129 130 // setup compiler command line 131 buildsettings.libs = null; 132 buildsettings.lflags = null; 133 buildsettings.addDFlags("-c", "-of"~tempobj.toNativeString()); 134 buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !f.endsWith(".lib"))().array(); 135 settings.compiler.prepareBuildSettings(buildsettings, BuildSetting.commandLine); 136 137 logInfo("Compiling..."); 138 settings.compiler.invoke(buildsettings, settings.platform); 139 140 logInfo("Linking..."); 141 if( settings.run ) cleanup_files ~= exe_file_path; 142 settings.compiler.invokeLinker(lbuildsettings, settings.platform, [tempobj.toNativeString()]); 143 } 144 145 // run post-build commands 146 if( buildsettings.postBuildCommands.length ){ 147 logInfo("Running post-build commands..."); 148 runBuildCommands(buildsettings.postBuildCommands, buildsettings); 149 } 150 151 // copy files and run the executable 152 if (generate_binary && settings.run) { 153 if (buildsettings.targetType == TargetType.executable) { 154 if (buildsettings.workingDirectory.length) { 155 logDiagnostic("Switching to %s", (cwd ~ buildsettings.workingDirectory).toNativeString()); 156 chdir((cwd ~ buildsettings.workingDirectory).toNativeString()); 157 } 158 scope(exit) chdir(cwd.toNativeString()); 159 logInfo("Running %s...", exe_file_path.toNativeString()); 160 auto prg_pid = spawnProcess(exe_file_path.toNativeString() ~ settings.runArgs); 161 auto result = prg_pid.wait(); 162 enforce(result == 0, "Program exited with code "~to!string(result)); 163 } else logInfo("Target is a library. Skipping execution."); 164 } 165 } 166 }