Last active
April 10, 2026 18:43
-
-
Save Frityet/019adef7a9d772d29780abce0cd4cf49 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #ifndef ASYNCRT_BUILD_H | |
| #define ASYNCRT_BUILD_H | |
| #import <ObjFW/ObjFW.h> | |
| #pragma clang assume_nonnull begin | |
| @class Build; | |
| @class BuildBase; | |
| #define BUILD_TARGET [self declaredTargetForSelector: _cmd] | |
| @interface Target : OFObject | |
| @property (readonly, copy, nonatomic) OFString *name; | |
| @property (readonly, copy, nonatomic) OFString *artifactName; | |
| @property (readonly, nonatomic) Target *binary; | |
| @property (readonly, nonatomic) Target *staticLibrary; | |
| @property (readonly, nonatomic) Target *sharedLibrary; | |
| @property (readonly, nonatomic) Target *phony; | |
| @property (readonly, nonatomic) Target *defaultTarget; | |
| @property (readonly, nonatomic) Target *alwaysBuild; | |
| @property (readonly, nonatomic) id dependsOn; | |
| @property (readonly, nonatomic) id publiclyDependsOn; | |
| - (Target *)dependsOn: (id)dependencies; | |
| - (Target *)publiclyDependsOn: (id)dependencies; | |
| - (Target *)outputName: (OFString *)name; | |
| - (Target *)group: (OFString *)name; | |
| - (Target *)pmHeader: (OFString *)path; | |
| - (Target *)workingDirectory: (OFString *)path; | |
| - (Target *)files: (id)paths; | |
| - (Target *)inputs: (id)paths; | |
| - (Target *)includeDirs: (id)paths; | |
| - (Target *)publicIncludeDirs: (id)paths; | |
| - (Target *)defines: (id)defines; | |
| - (Target *)publicDefines: (id)defines; | |
| - (Target *)packages: (id)packages; | |
| - (Target *)publicPackages: (id)packages; | |
| - (Target *)cFlags: (id)flags; | |
| - (Target *)mFlags: (id)flags; | |
| - (Target *)ldFlags: (id)flags; | |
| - (Target *)links: (id)links; | |
| - (Target *)libraryDirs: (id)directories; | |
| - (Target *)fileCFlags: (OFString *)pattern flags: (id)flags; | |
| - (Target *)fileMFlags: (OFString *)pattern flags: (id)flags; | |
| @end | |
| @interface BuildBase : OFObject | |
| @property (copy, nonatomic) OFString *mode; | |
| @property (nonatomic) size_t jobs; | |
| @property (nonatomic) bool verbose; | |
| @property (readonly, copy, nonatomic) OFString *projectRoot; | |
| @property (readonly, copy, nonatomic) OFString *buildRoot; | |
| - (Target *)declaredTargetForSelector: (SEL)selector; | |
| - (nullable Target *)targetNamed: (OFString *)name; | |
| @end | |
| @interface Build : BuildBase | |
| @end | |
| @interface App : OFObject <OFApplicationDelegate> | |
| @end | |
| typedef enum { | |
| TargetKindBinary, | |
| TargetKindStaticLibrary, | |
| TargetKindSharedLibrary, | |
| TargetKindPhony | |
| } TargetKind; | |
| typedef enum { | |
| ActionKindCommand, | |
| ActionKindNoOp | |
| } ActionKind; | |
| @interface BuildBase () | |
| { | |
| @public | |
| OFString *_projectRoot; | |
| OFString *_mode; | |
| OFString *_platform; | |
| OFString *_architecture; | |
| OFString *_buildBaseRoot; | |
| OFString *_buildRoot; | |
| OFString *_objfwBinDirectory; | |
| OFString *_objfwConfigPath; | |
| OFString *_objfwCompilePath; | |
| OFString *_compiler; | |
| OFString *_archiver; | |
| OFDictionary *_subprocessEnvironment; | |
| OFMutableDictionary *_objfwPackageCPPFlagsByName; | |
| OFMutableDictionary *_objfwPackageStaticLibsByName; | |
| OFMutableDictionary *_objfwPackageLDFlagsByName; | |
| OFArray *_objfwCPPFlags; | |
| OFArray *_objfwObjCFlags; | |
| OFArray *_objfwArcFlags; | |
| OFArray *_objfwStaticLibs; | |
| OFArray *_objfwLDFlags; | |
| OFArray *_modeCFlags; | |
| OFArray *_modeMFlags; | |
| OFArray *_modeLDFlags; | |
| OFMutableArray *_globalIncludeDirs; | |
| OFMutableArray *_globalDefines; | |
| OFMutableArray *_globalCFlags; | |
| OFMutableArray *_globalMFlags; | |
| OFMutableArray *_globalLDFlags; | |
| OFMutableDictionary *_targetsByIdentifier; | |
| OFArray *_knownTargetSelectorNames; | |
| OFArray *_allProjectFileSubpaths; | |
| size_t _jobs; | |
| bool _verbose; | |
| } | |
| @end | |
| @interface Target () | |
| { | |
| @public | |
| Build *_build; | |
| SEL _selector; | |
| OFString *_name; | |
| OFString *_artifactName; | |
| OFString *_group; | |
| OFString *_pmHeader; | |
| OFString *_workingDirectory; | |
| TargetKind _kind; | |
| bool _configured; | |
| bool _configuring; | |
| bool _defaultTarget; | |
| bool _alwaysBuild; | |
| OFMutableArray *_sourcePatterns; | |
| OFMutableArray *_extraInputs; | |
| OFMutableArray *_includeDirs; | |
| OFMutableArray *_publicIncludeDirs; | |
| OFMutableArray *_defines; | |
| OFMutableArray *_publicDefines; | |
| OFMutableArray *_packages; | |
| OFMutableArray *_publicPackages; | |
| OFMutableArray *_cFlags; | |
| OFMutableArray *_mFlags; | |
| OFMutableArray *_ldFlags; | |
| OFMutableArray *_links; | |
| OFMutableArray *_libraryDirs; | |
| OFMutableArray *_dependencies; | |
| OFMutableArray *_fileCFlags; | |
| OFMutableArray *_fileMFlags; | |
| id _dependsOnProxy; | |
| id _publicDependsOnProxy; | |
| } | |
| - (instancetype)initWithBuild: (Build *)build selector: (SEL)selector; | |
| - (void)addDependencySelector: (SEL)selector publicDependency: (bool)isPublic; | |
| @end | |
| @interface BuildDependencySpec : OFObject | |
| { | |
| @public | |
| SEL _selector; | |
| bool _publicDependency; | |
| } | |
| + (instancetype)specWithSelector: (SEL)selector publicDependency: (bool)isPublic; | |
| @end | |
| @interface BuildFileFlagRule : OFObject | |
| { | |
| @public | |
| OFString *_pattern; | |
| OFArray *_flags; | |
| } | |
| + (instancetype)ruleWithPattern: (OFString *)pattern flags: (OFArray *)flags; | |
| @end | |
| @interface BuildDependencyProxy : OFObject | |
| { | |
| Target *_target; | |
| bool _publicDependency; | |
| } | |
| - (instancetype)initWithTarget: (Target *)target | |
| publicDependency: (bool)isPublic; | |
| - (void)captureDependencySelector: (SEL)selector; | |
| @end | |
| @interface BuildAction : OFObject | |
| { | |
| @public | |
| OFString *_key; | |
| OFString *_label; | |
| OFString *_signature; | |
| OFString *_signaturePath; | |
| OFArray *_argv; | |
| OFDictionary *_environment; | |
| OFArray *_inputs; | |
| OFArray *_outputs; | |
| OFMutableArray *_dependencies; | |
| OFMutableArray *_dependents; | |
| OFArray *(^_dynamicInputsProvider)(void); | |
| OFSubprocess *_process; | |
| size_t _remainingDependencies; | |
| ActionKind _kind; | |
| bool _alwaysBuild; | |
| bool _completed; | |
| bool _succeeded; | |
| bool _skipped; | |
| } | |
| + (instancetype)actionWithKey: (OFString *)key label: (OFString *)label; | |
| @end | |
| @interface BuildEngine : OFObject | |
| { | |
| Build *_build; | |
| OFArray *_requestedTargetNames; | |
| OFString *_runTargetName; | |
| OFArray *_runArguments; | |
| OFMutableDictionary *_finalActionsByTargetName; | |
| OFMutableDictionary *_exportedIncludeDirsByTargetName; | |
| OFMutableDictionary *_exportedDefinesByTargetName; | |
| OFMutableDictionary *_exportedPackagesByTargetName; | |
| OFMutableDictionary *_exportedLinkArtifactsByTargetName; | |
| OFMutableArray *_allActions; | |
| OFMutableArray *_readyActions; | |
| OFSubprocess *_runProcess; | |
| void (^_completion)(int); | |
| size_t _activeActions; | |
| int _exitStatus; | |
| bool _failed; | |
| bool _launchedRunTarget; | |
| } | |
| - (instancetype)initWithBuild: (Build *)build | |
| targetNames: (OFArray *)targetNames | |
| runTargetName: (nullable OFString *)runTargetName | |
| runArguments: (nullable OFArray *)runArguments | |
| completion: (void (^)(int status))completion; | |
| - (void)start; | |
| - (void)pollActionOutput: (BuildAction *)action; | |
| - (void)pollRunProcess; | |
| @end | |
| static OFString *BuildSelectorString(SEL selector) | |
| { | |
| return [OFString stringWithUTF8String: sel_getName(selector)]; | |
| } | |
| static bool BuildSelectorHasArguments(SEL selector) | |
| { | |
| return [BuildSelectorString(selector) containsString: @":"]; | |
| } | |
| static void BuildAppendFlattenedUniqueObject(OFMutableArray *array, id object) | |
| { | |
| if (object == nil) | |
| return; | |
| if ([object isKindOfClass: [OFArray class]]) { | |
| for (id item in (OFArray *)object) | |
| BuildAppendFlattenedUniqueObject(array, item); | |
| return; | |
| } | |
| if (![array containsObject: object]) | |
| [array addObject: object]; | |
| } | |
| static OFArray *BuildSplitWhitespace(OFString *string) | |
| { | |
| OFMutableArray *components = [[OFMutableArray alloc] init]; | |
| for (OFString *component in [string componentsSeparatedByCharactersInSet: | |
| [OFCharacterSet whitespaceCharacterSet]]) { | |
| if (component.length > 0) | |
| [components addObject: component]; | |
| } | |
| return components; | |
| } | |
| static OFString *BuildExpandTilde(OFString *path) | |
| { | |
| OFString *home; | |
| if (![path hasPrefix: @"~/"]) | |
| return path; | |
| home = [[OFApplication environment] objectForKey: @"HOME"]; | |
| if (home == nil) | |
| return path; | |
| return [home stringByAppendingPathComponent: | |
| [path substringFromIndex: 2]]; | |
| } | |
| static OFString *BuildSanitizePathComponent(OFString *component) | |
| { | |
| OFMutableString *sanitized = [component.lowercaseString mutableCopy]; | |
| [sanitized replaceOccurrencesOfString: @" " withString: @"-"]; | |
| [sanitized replaceOccurrencesOfString: @"/" withString: @"-"]; | |
| return sanitized; | |
| } | |
| static OFString *BuildHostArchitecture(void) | |
| { | |
| #if defined(OF_AMD64) || defined(__x86_64__) | |
| return @"x86_64"; | |
| #elif defined(OF_X86) || defined(__i386__) | |
| return @"x86"; | |
| #elif defined(__aarch64__) | |
| return @"aarch64"; | |
| #elif defined(__arm__) | |
| return @"arm"; | |
| #elif defined(__riscv) && (__riscv_xlen == 64) | |
| return @"riscv64"; | |
| #else | |
| return @"unknown"; | |
| #endif | |
| } | |
| static OFString *BuildDynamicLibrarySuffix(void) | |
| { | |
| #ifdef OF_WINDOWS | |
| return @".dll"; | |
| #elif defined(__APPLE__) | |
| return @".dylib"; | |
| #else | |
| return @".so"; | |
| #endif | |
| } | |
| static OFString *BuildExecutableSuffix(void) | |
| { | |
| #ifdef OF_WINDOWS | |
| return @".exe"; | |
| #else | |
| return @""; | |
| #endif | |
| } | |
| static bool BuildGlobMatchesCString(const char *pattern, const char *string) | |
| { | |
| if (*pattern == '\0') | |
| return (*string == '\0'); | |
| if (*pattern == '*') { | |
| if (*(pattern + 1) == '*') { | |
| pattern += 2; | |
| while (*pattern == '*') | |
| pattern++; | |
| if (*pattern == '\0') | |
| return true; | |
| while (true) { | |
| if (BuildGlobMatchesCString(pattern, string)) | |
| return true; | |
| if (*string == '\0') | |
| return false; | |
| string++; | |
| } | |
| } | |
| pattern++; | |
| while (true) { | |
| if (BuildGlobMatchesCString(pattern, string)) | |
| return true; | |
| if (*string == '\0' || *string == '/') | |
| return false; | |
| string++; | |
| } | |
| } | |
| if (*pattern == '?') { | |
| if (*string == '\0' || *string == '/') | |
| return false; | |
| return BuildGlobMatchesCString(pattern + 1, string + 1); | |
| } | |
| if (*pattern != *string) | |
| return false; | |
| return BuildGlobMatchesCString(pattern + 1, string + 1); | |
| } | |
| static bool BuildPatternMatchesPath(OFString *pattern, OFString *path) | |
| { | |
| return BuildGlobMatchesCString(pattern.UTF8String, path.UTF8String); | |
| } | |
| static bool BuildPatternHasWildcard(OFString *pattern) | |
| { | |
| return ([pattern containsString: @"*"] || [pattern containsString: @"?"]); | |
| } | |
| static void BuildAppendUniqueString(OFMutableArray *array, OFString *string) | |
| { | |
| if (string == nil) | |
| return; | |
| if (![array containsObject: string]) | |
| [array addObject: string]; | |
| } | |
| static void BuildAppendUniqueStrings(OFMutableArray *array, OFArray *strings) | |
| { | |
| for (OFString *string in strings) | |
| BuildAppendUniqueString(array, string); | |
| } | |
| static OFString *BuildRelativePath(OFString *root, OFString *path) | |
| { | |
| OFString *standardRoot = root.stringByStandardizingPath; | |
| OFString *standardPath = path.stringByStandardizingPath; | |
| OFString *prefix = [standardRoot stringByAppendingString: @"/"]; | |
| if ([standardPath isEqual: standardRoot]) | |
| return @"."; | |
| if ([standardPath hasPrefix: prefix]) | |
| return [standardPath substringFromIndex: prefix.length]; | |
| return standardPath; | |
| } | |
| static OFString *BuildProjectAbsolutePath(Build *build, OFString *path) | |
| { | |
| OFString *expanded; | |
| if (path == nil) | |
| return nil; | |
| expanded = BuildExpandTilde(path); | |
| if (expanded.absolutePath) | |
| return expanded.stringByStandardizingPath; | |
| return [[build->_projectRoot stringByAppendingPathComponent: expanded] | |
| stringByStandardizingPath]; | |
| } | |
| static OFString *BuildDisplayPath(Build *build, OFString *path) | |
| { | |
| return BuildRelativePath(build->_projectRoot, path); | |
| } | |
| static OFString *BuildSearchPathForExecutable(OFString *name) | |
| { | |
| OFFileManager *fileManager = [OFFileManager defaultManager]; | |
| OFString *environmentPath = [[OFApplication environment] objectForKey: @"PATH"]; | |
| if (environmentPath == nil) | |
| return nil; | |
| for (OFString *directory in [environmentPath componentsSeparatedByString: @":"]) { | |
| OFString *candidate; | |
| if (directory.length == 0) | |
| continue; | |
| candidate = [directory stringByAppendingPathComponent: name]; | |
| if ([fileManager fileExistsAtPath: candidate] && | |
| ![fileManager directoryExistsAtPath: candidate]) | |
| return candidate; | |
| } | |
| return nil; | |
| } | |
| static OFString *BuildSearchXmakePackageTool(OFString *name) | |
| { | |
| OFFileManager *fileManager = [OFFileManager defaultManager]; | |
| OFString *root = BuildExpandTilde(@"~/.xmake/packages/o/objfw"); | |
| OFString *bestCandidate = nil; | |
| OFDate *bestDate = nil; | |
| if (![fileManager directoryExistsAtPath: root]) | |
| return nil; | |
| for (OFString *subpath in [fileManager subpathsOfDirectoryAtPath: root]) { | |
| OFString *candidate; | |
| OFDate *candidateDate; | |
| if ([subpath hasSuffix: | |
| [@"/bin/" stringByAppendingString: name]]) | |
| candidate = (subpath.absolutePath | |
| ? subpath.stringByStandardizingPath | |
| : [root stringByAppendingPathComponent: subpath]); | |
| else | |
| continue; | |
| candidateDate = [fileManager attributesOfItemAtPath: candidate] | |
| .fileModificationDate; | |
| if (bestDate == nil || | |
| [candidateDate compare: bestDate] == OFOrderedDescending) { | |
| bestCandidate = candidate; | |
| bestDate = candidateDate; | |
| } | |
| } | |
| return bestCandidate; | |
| } | |
| static OFString *BuildFindExecutable(OFString *environmentVariable, | |
| OFString *name) | |
| { | |
| OFFileManager *fileManager = [OFFileManager defaultManager]; | |
| OFString *candidate = [[OFApplication environment] | |
| objectForKey: environmentVariable]; | |
| if (candidate != nil) { | |
| candidate = BuildExpandTilde(candidate); | |
| if ([fileManager fileExistsAtPath: candidate] && | |
| ![fileManager directoryExistsAtPath: candidate]) | |
| return candidate; | |
| } | |
| candidate = BuildSearchPathForExecutable(name); | |
| if (candidate != nil) | |
| return candidate; | |
| return BuildSearchXmakePackageTool(name); | |
| } | |
| static OFDictionary *BuildEnvironmentByPrependingPathComponent( | |
| OFString *directory) | |
| { | |
| OFMutableDictionary *environment = [[[OFApplication environment] mutableCopy] | |
| ?: [[OFMutableDictionary alloc] init] mutableCopy]; | |
| OFString *existingPath = [environment objectForKey: @"PATH"]; | |
| OFString *prefixedPath; | |
| if (directory == nil || directory.length == 0) | |
| return environment; | |
| if ([existingPath isEqual: directory] || | |
| [existingPath hasPrefix: | |
| [directory stringByAppendingString: @":"]]) | |
| return environment; | |
| prefixedPath = (existingPath.length > 0 | |
| ? [OFString stringWithFormat: @"%@:%@", directory, existingPath] | |
| : directory); | |
| [environment setObject: prefixedPath forKey: @"PATH"]; | |
| return environment; | |
| } | |
| static OFString *BuildRunTool(OFString *program, OFArray *arguments, | |
| OFDictionary *environment) | |
| { | |
| OFSubprocess *process; | |
| OFString *output; | |
| process = [OFSubprocess subprocessWithProgram: program | |
| programName: program | |
| arguments: arguments | |
| environment: environment]; | |
| [process closeForWriting]; | |
| output = [[process readString] stringByDeletingEnclosingWhitespaces]; | |
| if ([process waitForTermination] != 0) | |
| @throw [OFInitializationFailedException exceptionWithClass: Build.class]; | |
| return output; | |
| } | |
| static OFArray *BuildObjFWPackageConfigFlags(Build *build, OFString *package, | |
| OFArray *configArguments, OFMutableDictionary *cache) | |
| { | |
| OFArray *cached = [cache objectForKey: package]; | |
| OFMutableArray *arguments; | |
| if (cached != nil) | |
| return cached; | |
| arguments = [[OFMutableArray alloc] init]; | |
| [arguments addObject: @"--package"]; | |
| [arguments addObject: package]; | |
| [arguments addObjectsFromArray: configArguments]; | |
| cached = [BuildSplitWhitespace(BuildRunTool(build->_objfwConfigPath, | |
| arguments, build->_subprocessEnvironment)) copy]; | |
| [cache setObject: cached forKey: package]; | |
| return cached; | |
| } | |
| static OFArray *BuildResolvePackageCPPFlags(Build *build, OFArray *packages) | |
| { | |
| OFMutableArray *flags = [[OFMutableArray alloc] init]; | |
| for (OFString *package in packages) | |
| BuildAppendUniqueStrings(flags, BuildObjFWPackageConfigFlags(build, | |
| package, [OFArray arrayWithObject: @"--cppflags"], | |
| build->_objfwPackageCPPFlagsByName)); | |
| return flags; | |
| } | |
| static OFArray *BuildResolvePackageStaticLibs(Build *build, OFArray *packages) | |
| { | |
| OFMutableArray *flags = [[OFMutableArray alloc] init]; | |
| for (OFString *package in packages) | |
| BuildAppendUniqueStrings(flags, BuildObjFWPackageConfigFlags(build, | |
| package, [OFArray arrayWithObject: @"--libs"], | |
| build->_objfwPackageStaticLibsByName)); | |
| return flags; | |
| } | |
| static OFArray *BuildResolvePackageLDFlags(Build *build, OFArray *packages) | |
| { | |
| OFMutableArray *flags = [[OFMutableArray alloc] init]; | |
| for (OFString *package in packages) | |
| BuildAppendUniqueStrings(flags, BuildObjFWPackageConfigFlags(build, | |
| package, [OFArray arrayWithObjects: @"--ldflags", @"--rpath", nil], | |
| build->_objfwPackageLDFlagsByName)); | |
| return flags; | |
| } | |
| static void BuildRefreshMode(Build *build) | |
| { | |
| OFMutableArray *modeCFlags = [[OFMutableArray alloc] init]; | |
| OFMutableArray *modeMFlags = [[OFMutableArray alloc] init]; | |
| OFMutableArray *modeLDFlags = [[OFMutableArray alloc] init]; | |
| OFString *normalizedMode = build->_mode.lowercaseString; | |
| build->_buildRoot = [[[[build->_buildBaseRoot | |
| stringByAppendingPathComponent: build->_platform] | |
| stringByAppendingPathComponent: build->_architecture] | |
| stringByAppendingPathComponent: normalizedMode] stringByStandardizingPath]; | |
| if ([normalizedMode isEqual: @"release"]) { | |
| [modeCFlags addObject: @"-O3"]; | |
| [modeCFlags addObject: @"-DNDEBUG"]; | |
| [modeMFlags addObjectsFromArray: modeCFlags]; | |
| } else if ([normalizedMode isEqual: @"asan"]) { | |
| [modeCFlags addObjectsFromArray: [OFArray arrayWithObjects: | |
| @"-O1", @"-g", @"-fsanitize=address", @"-fsanitize=undefined", | |
| nil]]; | |
| [modeMFlags addObjectsFromArray: modeCFlags]; | |
| [modeLDFlags addObjectsFromArray: [OFArray arrayWithObjects: | |
| @"-fsanitize=address", @"-fsanitize=undefined", nil]]; | |
| } else if ([normalizedMode isEqual: @"tsan"]) { | |
| [modeCFlags addObjectsFromArray: [OFArray arrayWithObjects: | |
| @"-O1", @"-g", @"-fsanitize=thread", nil]]; | |
| [modeMFlags addObjectsFromArray: modeCFlags]; | |
| [modeLDFlags addObject: @"-fsanitize=thread"]; | |
| } else { | |
| [modeCFlags addObjectsFromArray: [OFArray arrayWithObjects: | |
| @"-O0", @"-g", nil]]; | |
| [modeMFlags addObjectsFromArray: modeCFlags]; | |
| } | |
| build->_modeCFlags = [modeCFlags copy]; | |
| build->_modeMFlags = [modeMFlags copy]; | |
| build->_modeLDFlags = [modeLDFlags copy]; | |
| } | |
| static void BuildConfigureToolchain(Build *build) | |
| { | |
| OFFileManager *fileManager = [OFFileManager defaultManager]; | |
| OFMutableArray *baseMFlags; | |
| OFString *archiver; | |
| OFString *siblingConfigPath; | |
| build->_objfwCompilePath = [BuildFindExecutable(@"OBJFW_COMPILE", | |
| @"objfw-compile") copy]; | |
| if (build->_objfwCompilePath == nil) { | |
| [OFStdErr writeLine: | |
| @"Unable to find objfw-compile. Set OBJFW_COMPILE or install ObjFW's tools."]; | |
| @throw [OFInitializationFailedException exceptionWithClass: Build.class]; | |
| } | |
| build->_objfwBinDirectory = | |
| [[build->_objfwCompilePath stringByDeletingLastPathComponent] copy]; | |
| build->_subprocessEnvironment = [BuildEnvironmentByPrependingPathComponent( | |
| build->_objfwBinDirectory) copy]; | |
| siblingConfigPath = [build->_objfwBinDirectory | |
| stringByAppendingPathComponent: @"objfw-config"]; | |
| if ([fileManager fileExistsAtPath: siblingConfigPath] && | |
| ![fileManager directoryExistsAtPath: siblingConfigPath]) | |
| build->_objfwConfigPath = [siblingConfigPath copy]; | |
| else | |
| build->_objfwConfigPath = [BuildFindExecutable(@"OBJFW_CONFIG", | |
| @"objfw-config") copy]; | |
| if (build->_objfwConfigPath == nil) { | |
| [OFStdErr writeLine: | |
| @"Unable to find objfw-config. Set OBJFW_CONFIG or install ObjFW's tools."]; | |
| @throw [OFInitializationFailedException exceptionWithClass: Build.class]; | |
| } | |
| build->_compiler = [[BuildRunTool(build->_objfwConfigPath, | |
| [OFArray arrayWithObject: @"--objc"], | |
| build->_subprocessEnvironment) | |
| stringByDeletingEnclosingWhitespaces] copy]; | |
| archiver = BuildFindExecutable(@"AR", @"llvm-ar"); | |
| if (archiver == nil) | |
| archiver = BuildFindExecutable(@"AR", @"ar"); | |
| if (archiver == nil) | |
| archiver = @"ar"; | |
| build->_archiver = [archiver copy]; | |
| build->_objfwCPPFlags = [BuildSplitWhitespace(BuildRunTool( | |
| build->_objfwConfigPath, [OFArray arrayWithObject: @"--cppflags"], | |
| build->_subprocessEnvironment)) copy]; | |
| build->_objfwObjCFlags = [BuildSplitWhitespace(BuildRunTool( | |
| build->_objfwConfigPath, [OFArray arrayWithObject: @"--objcflags"], | |
| build->_subprocessEnvironment)) copy]; | |
| build->_objfwArcFlags = [BuildSplitWhitespace(BuildRunTool( | |
| build->_objfwConfigPath, [OFArray arrayWithObject: @"--arc"], | |
| build->_subprocessEnvironment)) copy]; | |
| build->_objfwStaticLibs = [BuildSplitWhitespace(BuildRunTool( | |
| build->_objfwConfigPath, [OFArray arrayWithObject: @"--static-libs"], | |
| build->_subprocessEnvironment)) copy]; | |
| build->_objfwLDFlags = [BuildSplitWhitespace(BuildRunTool( | |
| build->_objfwConfigPath, | |
| [OFArray arrayWithObjects: @"--ldflags", @"--rpath", nil], | |
| build->_subprocessEnvironment)) copy]; | |
| baseMFlags = [[OFMutableArray alloc] init]; | |
| [baseMFlags addObjectsFromArray: [OFArray arrayWithObjects: | |
| @"-std=gnu23", | |
| @"-Werror", | |
| @"-Wall", | |
| @"-Wextra", | |
| @"-fms-extensions", | |
| @"-Wno-microsoft", | |
| @"-Wno-unused-function", | |
| @"-Wanon-enum-enum-conversion", | |
| @"-Wassign-enum", | |
| @"-Wenum-conversion", | |
| @"-Wenum-enum-conversion", | |
| @"-Wnull-dereference", | |
| @"-Wnull-conversion", | |
| @"-Wnullability-completeness", | |
| @"-Wnullable-to-nonnull-conversion", | |
| @"-Wno-auto-var-id", | |
| @"-Wno-missing-braces", | |
| nil]]; | |
| build->_globalMFlags = baseMFlags; | |
| build->_globalCFlags = [[OFMutableArray alloc] init]; | |
| build->_globalIncludeDirs = [[OFMutableArray alloc] init]; | |
| build->_globalDefines = [[OFMutableArray alloc] init]; | |
| build->_globalLDFlags = [[OFMutableArray alloc] init]; | |
| [build->_globalIncludeDirs addObject: @"src"]; | |
| if ([build->_platform isEqual: @"linux"]) { | |
| [build->_globalMFlags addObject: @"-fno-omit-frame-pointer"]; | |
| [build->_globalLDFlags addObject: @"-rdynamic"]; | |
| } | |
| BuildRefreshMode(build); | |
| } | |
| static bool BuildSelectorNameIsReserved(OFString *name) | |
| { | |
| return ([name isEqual: @"init"] || | |
| [name isEqual: @"mode"] || | |
| [name isEqual: @"jobs"] || | |
| [name isEqual: @"verbose"] || | |
| [name isEqual: @"projectRoot"] || | |
| [name isEqual: @"buildRoot"]); | |
| } | |
| static OFArray *BuildKnownTargetSelectorNames(Build *build) | |
| { | |
| Method *methodList; | |
| unsigned int count = 0; | |
| OFMutableArray *names; | |
| if (build->_knownTargetSelectorNames != nil) | |
| return build->_knownTargetSelectorNames; | |
| methodList = class_copyMethodList(build.class, &count); | |
| names = [[OFMutableArray alloc] init]; | |
| @try { | |
| for (unsigned int index = 0; index < count; index++) { | |
| SEL selector = method_getName(methodList[index]); | |
| OFString *name = BuildSelectorString(selector); | |
| if (BuildSelectorHasArguments(selector)) | |
| continue; | |
| if (BuildSelectorNameIsReserved(name)) | |
| continue; | |
| [names addObject: name]; | |
| } | |
| } @finally { | |
| OFFreeMemory(methodList); | |
| } | |
| [names sort]; | |
| build->_knownTargetSelectorNames = [names copy]; | |
| return build->_knownTargetSelectorNames; | |
| } | |
| static bool BuildIsKnownTargetSelector(Build *build, SEL selector) | |
| { | |
| return [BuildKnownTargetSelectorNames(build) | |
| containsObject: BuildSelectorString(selector)]; | |
| } | |
| static void BuildEnsureProjectFileCache(Build *build) | |
| { | |
| OFFileManager *fileManager = [OFFileManager defaultManager]; | |
| OFMutableArray *files; | |
| if (build->_allProjectFileSubpaths != nil) | |
| return; | |
| files = [[OFMutableArray alloc] init]; | |
| for (OFString *subpath in [fileManager | |
| subpathsOfDirectoryAtPath: build->_projectRoot]) { | |
| OFString *absolutePath; | |
| OFString *relativePath; | |
| absolutePath = (subpath.absolutePath | |
| ? subpath.stringByStandardizingPath | |
| : [[build->_projectRoot stringByAppendingPathComponent: subpath] | |
| stringByStandardizingPath]); | |
| relativePath = BuildRelativePath(build->_projectRoot, absolutePath); | |
| if (![fileManager directoryExistsAtPath: absolutePath]) | |
| [files addObject: relativePath]; | |
| } | |
| [files sort]; | |
| build->_allProjectFileSubpaths = [files copy]; | |
| } | |
| static OFString *BuildNormalizeProjectPattern(Build *build, OFString *pattern) | |
| { | |
| OFString *expanded = BuildExpandTilde(pattern); | |
| OFString *prefix; | |
| if (!expanded.absolutePath) | |
| return expanded.stringByStandardizingPath; | |
| prefix = [build->_projectRoot stringByAppendingString: @"/"]; | |
| if ([expanded hasPrefix: prefix]) | |
| return [expanded substringFromIndex: prefix.length]; | |
| return expanded.stringByStandardizingPath; | |
| } | |
| static OFArray *BuildResolvedSourcePaths(Build *build, Target *target) | |
| { | |
| OFFileManager *fileManager = [OFFileManager defaultManager]; | |
| OFMutableArray *resolved = [[OFMutableArray alloc] init]; | |
| OFMutableDictionary *seen = [[OFMutableDictionary alloc] init]; | |
| BuildEnsureProjectFileCache(build); | |
| for (OFString *patternSpec in target->_sourcePatterns) { | |
| OFArray *components = [patternSpec componentsSeparatedByString: @"|"]; | |
| OFString *includePattern; | |
| OFMutableArray *excludePatterns = [[OFMutableArray alloc] init]; | |
| if (components.count == 0) | |
| continue; | |
| includePattern = BuildNormalizeProjectPattern(build, | |
| [components objectAtIndex: 0]); | |
| for (size_t index = 1; index < components.count; index++) { | |
| [excludePatterns addObject: BuildNormalizeProjectPattern(build, | |
| [components objectAtIndex: index])]; | |
| } | |
| if (!BuildPatternHasWildcard(includePattern)) { | |
| OFString *absolutePath = | |
| [build->_projectRoot stringByAppendingPathComponent: includePattern]; | |
| if ([fileManager fileExistsAtPath: absolutePath] && | |
| ![fileManager directoryExistsAtPath: absolutePath] && | |
| [seen objectForKey: includePattern] == nil) { | |
| [seen setObject: @YES forKey: includePattern]; | |
| [resolved addObject: includePattern]; | |
| } | |
| continue; | |
| } | |
| for (OFString *subpath in build->_allProjectFileSubpaths) { | |
| bool excluded = false; | |
| if (!BuildPatternMatchesPath(includePattern, subpath)) | |
| continue; | |
| for (OFString *excludePattern in excludePatterns) { | |
| if (BuildPatternMatchesPath(excludePattern, subpath)) { | |
| excluded = true; | |
| break; | |
| } | |
| } | |
| if (!excluded && [seen objectForKey: subpath] == nil) { | |
| [seen setObject: @YES forKey: subpath]; | |
| [resolved addObject: subpath]; | |
| } | |
| } | |
| } | |
| [resolved sort]; | |
| return resolved; | |
| } | |
| static OFArray *BuildParseDepfile(OFString *path) | |
| { | |
| OFFileManager *fileManager = [OFFileManager defaultManager]; | |
| OFMutableString *contents; | |
| OFMutableArray *dependencies; | |
| OFRange colon; | |
| if (![fileManager fileExistsAtPath: path]) | |
| return nil; | |
| contents = [[OFString stringWithContentsOfFile: path] mutableCopy]; | |
| [contents replaceOccurrencesOfString: @"\\\n" withString: @""]; | |
| colon = [contents rangeOfString: @":"]; | |
| if (colon.location == OFNotFound) | |
| return nil; | |
| dependencies = [[OFMutableArray alloc] init]; | |
| for (OFString *token in [[contents substringFromIndex: colon.location + 1] | |
| componentsSeparatedByCharactersInSet: | |
| [OFCharacterSet whitespaceCharacterSet]]) { | |
| if (token.length > 0) | |
| [dependencies addObject: token]; | |
| } | |
| return dependencies; | |
| } | |
| static OFString *BuildActionSignature(BuildAction *action) | |
| { | |
| OFMutableArray *parts = [[OFMutableArray alloc] init]; | |
| if (action->_argv != nil) | |
| [parts addObject: [action->_argv componentsJoinedByString: @"\n"]]; | |
| if (action->_inputs != nil) | |
| [parts addObject: [action->_inputs componentsJoinedByString: @"\n"]]; | |
| if (action->_outputs != nil) | |
| [parts addObject: [action->_outputs componentsJoinedByString: @"\n"]]; | |
| return [parts componentsJoinedByString: @"\n--\n"]; | |
| } | |
| static bool BuildActionIsUpToDate(BuildAction *action) | |
| { | |
| OFFileManager *fileManager = [OFFileManager defaultManager]; | |
| OFDate *oldestOutput = nil; | |
| OFDate *newestInput = nil; | |
| OFString *storedSignature; | |
| OFArray *dynamicInputs; | |
| if (action->_alwaysBuild || action->_kind != ActionKindCommand) | |
| return false; | |
| if (action->_outputs.count == 0 || action->_signaturePath == nil) | |
| return false; | |
| for (OFString *output in action->_outputs) { | |
| OFDate *modificationDate; | |
| if (![fileManager fileExistsAtPath: output]) | |
| return false; | |
| modificationDate = [fileManager attributesOfItemAtPath: output] | |
| .fileModificationDate; | |
| if (oldestOutput == nil || | |
| [modificationDate compare: oldestOutput] == OFOrderedAscending) | |
| oldestOutput = modificationDate; | |
| } | |
| if (![fileManager fileExistsAtPath: action->_signaturePath]) | |
| return false; | |
| storedSignature = [[OFString stringWithContentsOfFile: action->_signaturePath] | |
| stringByDeletingEnclosingWhitespaces]; | |
| if (![storedSignature isEqual: action->_signature]) | |
| return false; | |
| dynamicInputs = (action->_dynamicInputsProvider != nil | |
| ? action->_dynamicInputsProvider() | |
| : nil); | |
| for (OFString *input in action->_inputs) { | |
| OFDate *modificationDate; | |
| if (![fileManager fileExistsAtPath: input]) | |
| return false; | |
| modificationDate = [fileManager attributesOfItemAtPath: input] | |
| .fileModificationDate; | |
| if (newestInput == nil || | |
| [modificationDate compare: newestInput] == OFOrderedDescending) | |
| newestInput = modificationDate; | |
| } | |
| for (OFString *input in dynamicInputs) { | |
| OFDate *modificationDate; | |
| if (![fileManager fileExistsAtPath: input]) | |
| return false; | |
| modificationDate = [fileManager attributesOfItemAtPath: input] | |
| .fileModificationDate; | |
| if (newestInput == nil || | |
| [modificationDate compare: newestInput] == OFOrderedDescending) | |
| newestInput = modificationDate; | |
| } | |
| if (newestInput == nil) | |
| return false; | |
| return ([oldestOutput compare: newestInput] != OFOrderedAscending); | |
| } | |
| static void BuildAddActionDependency(BuildAction *action, BuildAction *dependency) | |
| { | |
| if (dependency == nil) | |
| return; | |
| if ([action->_dependencies containsObject: dependency]) | |
| return; | |
| [action->_dependencies addObject: dependency]; | |
| [dependency->_dependents addObject: action]; | |
| } | |
| static OFArray *BuildResolvePathList(Build *build, OFArray *paths) | |
| { | |
| OFMutableArray *resolved = [[OFMutableArray alloc] init]; | |
| for (OFString *path in paths) | |
| [resolved addObject: BuildProjectAbsolutePath(build, path)]; | |
| return resolved; | |
| } | |
| static OFArray *BuildResolveDefineFlags(OFArray *defines) | |
| { | |
| OFMutableArray *flags = [[OFMutableArray alloc] init]; | |
| for (OFString *define in defines) { | |
| if ([define hasPrefix: @"-D"]) | |
| [flags addObject: define]; | |
| else | |
| [flags addObject: [@"-D" stringByAppendingString: define]]; | |
| } | |
| return flags; | |
| } | |
| static OFArray *BuildResolveIncludeFlags(Build *build, OFArray *paths) | |
| { | |
| OFMutableArray *flags = [[OFMutableArray alloc] init]; | |
| for (OFString *path in paths) { | |
| [flags addObject: @"-I"]; | |
| [flags addObject: BuildProjectAbsolutePath(build, path)]; | |
| } | |
| return flags; | |
| } | |
| static OFArray *BuildResolveLibraryDirectoryFlags(Build *build, OFArray *paths) | |
| { | |
| OFMutableArray *flags = [[OFMutableArray alloc] init]; | |
| for (OFString *path in paths) { | |
| [flags addObject: @"-L"]; | |
| [flags addObject: BuildProjectAbsolutePath(build, path)]; | |
| } | |
| return flags; | |
| } | |
| static OFArray *BuildResolveLinkFlags(OFArray *links) | |
| { | |
| OFMutableArray *flags = [[OFMutableArray alloc] init]; | |
| for (OFString *link in links) { | |
| if ([link hasPrefix: @"-"] || link.absolutePath || | |
| [link containsString: @"/"]) { | |
| [flags addObject: link]; | |
| } else { | |
| [flags addObject: [@"-l" stringByAppendingString: link]]; | |
| } | |
| } | |
| return flags; | |
| } | |
| static OFArray *BuildCollectMatchingFlags(OFArray *rules, OFString *sourcePath) | |
| { | |
| OFMutableArray *flags = [[OFMutableArray alloc] init]; | |
| for (BuildFileFlagRule *rule in rules) { | |
| if (BuildPatternMatchesPath(rule->_pattern, sourcePath)) | |
| BuildAppendUniqueStrings(flags, rule->_flags); | |
| } | |
| return flags; | |
| } | |
| static void BuildAppendDependenciesFromObject(Target *target, id object, | |
| bool isPublic) | |
| { | |
| if (object == nil) | |
| return; | |
| if ([object isKindOfClass: [OFArray class]]) { | |
| for (id item in (OFArray *)object) | |
| BuildAppendDependenciesFromObject(target, item, isPublic); | |
| return; | |
| } | |
| if ([object isKindOfClass: [Target class]]) { | |
| [target addDependencySelector: ((Target *)object)->_selector | |
| publicDependency: isPublic]; | |
| return; | |
| } | |
| if ([object isKindOfClass: [OFString class]]) { | |
| [target addDependencySelector: | |
| sel_registerName(((OFString *)object).UTF8String) | |
| publicDependency: isPublic]; | |
| return; | |
| } | |
| } | |
| static id BuildDependencyProxyIMP(id self, SEL _cmd) | |
| { | |
| [(BuildDependencyProxy *)self captureDependencySelector: _cmd]; | |
| return self; | |
| } | |
| @implementation BuildDependencySpec | |
| + (instancetype)specWithSelector: (SEL)selector publicDependency: (bool)isPublic | |
| { | |
| BuildDependencySpec *spec = [[self alloc] init]; | |
| spec->_selector = selector; | |
| spec->_publicDependency = isPublic; | |
| return spec; | |
| } | |
| @end | |
| @implementation BuildFileFlagRule | |
| + (instancetype)ruleWithPattern: (OFString *)pattern flags: (OFArray *)flags | |
| { | |
| BuildFileFlagRule *rule = [[self alloc] init]; | |
| rule->_pattern = [pattern copy]; | |
| rule->_flags = [flags copy]; | |
| return rule; | |
| } | |
| @end | |
| @implementation BuildDependencyProxy | |
| + (bool)resolveInstanceMethod: (SEL)selector | |
| { | |
| if ([Target instancesRespondToSelector: selector]) | |
| return [super resolveInstanceMethod: selector]; | |
| if (!BuildSelectorHasArguments(selector)) { | |
| class_addMethod(self, selector, (IMP)BuildDependencyProxyIMP, "@@:"); | |
| return true; | |
| } | |
| return [super resolveInstanceMethod: selector]; | |
| } | |
| - (instancetype)initWithTarget: (Target *)target | |
| publicDependency: (bool)isPublic | |
| { | |
| self = [super init]; | |
| _target = target; | |
| _publicDependency = isPublic; | |
| return self; | |
| } | |
| - (void)captureDependencySelector: (SEL)selector | |
| { | |
| [_target addDependencySelector: selector publicDependency: _publicDependency]; | |
| } | |
| - (nullable id)forwardingTargetForSelector: (SEL)selector | |
| { | |
| if ([Target instancesRespondToSelector: selector]) | |
| return _target; | |
| return [super forwardingTargetForSelector: selector]; | |
| } | |
| @end | |
| @implementation BuildAction | |
| + (instancetype)actionWithKey: (OFString *)key label: (OFString *)label | |
| { | |
| BuildAction *action = [[self alloc] init]; | |
| action->_key = [key copy]; | |
| action->_label = [label copy]; | |
| action->_dependencies = [[OFMutableArray alloc] init]; | |
| action->_dependents = [[OFMutableArray alloc] init]; | |
| return action; | |
| } | |
| @end | |
| @implementation Target | |
| - (instancetype)initWithBuild: (Build *)build selector: (SEL)selector | |
| { | |
| self = [super init]; | |
| _build = build; | |
| _selector = selector; | |
| _name = [BuildSelectorString(selector) copy]; | |
| _kind = TargetKindBinary; | |
| _sourcePatterns = [[OFMutableArray alloc] init]; | |
| _extraInputs = [[OFMutableArray alloc] init]; | |
| _includeDirs = [[OFMutableArray alloc] init]; | |
| _publicIncludeDirs = [[OFMutableArray alloc] init]; | |
| _defines = [[OFMutableArray alloc] init]; | |
| _publicDefines = [[OFMutableArray alloc] init]; | |
| _packages = [[OFMutableArray alloc] init]; | |
| _publicPackages = [[OFMutableArray alloc] init]; | |
| _cFlags = [[OFMutableArray alloc] init]; | |
| _mFlags = [[OFMutableArray alloc] init]; | |
| _ldFlags = [[OFMutableArray alloc] init]; | |
| _links = [[OFMutableArray alloc] init]; | |
| _libraryDirs = [[OFMutableArray alloc] init]; | |
| _dependencies = [[OFMutableArray alloc] init]; | |
| _fileCFlags = [[OFMutableArray alloc] init]; | |
| _fileMFlags = [[OFMutableArray alloc] init]; | |
| return self; | |
| } | |
| - (OFString *)name | |
| { | |
| return _name; | |
| } | |
| - (OFString *)artifactName | |
| { | |
| return (_artifactName != nil ? _artifactName : _name); | |
| } | |
| - (Target *)binary | |
| { | |
| _kind = TargetKindBinary; | |
| return self; | |
| } | |
| - (Target *)staticLibrary | |
| { | |
| _kind = TargetKindStaticLibrary; | |
| return self; | |
| } | |
| - (Target *)sharedLibrary | |
| { | |
| _kind = TargetKindSharedLibrary; | |
| return self; | |
| } | |
| - (Target *)phony | |
| { | |
| _kind = TargetKindPhony; | |
| return self; | |
| } | |
| - (Target *)defaultTarget | |
| { | |
| _defaultTarget = true; | |
| return self; | |
| } | |
| - (Target *)alwaysBuild | |
| { | |
| _alwaysBuild = true; | |
| return self; | |
| } | |
| - (id)dependsOn | |
| { | |
| if (_dependsOnProxy == nil) | |
| _dependsOnProxy = [[BuildDependencyProxy alloc] | |
| initWithTarget: self | |
| publicDependency: false]; | |
| return _dependsOnProxy; | |
| } | |
| - (id)publiclyDependsOn | |
| { | |
| if (_publicDependsOnProxy == nil) | |
| _publicDependsOnProxy = [[BuildDependencyProxy alloc] | |
| initWithTarget: self | |
| publicDependency: true]; | |
| return _publicDependsOnProxy; | |
| } | |
| - (Target *)dependsOn: (id)dependencies | |
| { | |
| BuildAppendDependenciesFromObject(self, dependencies, false); | |
| return self; | |
| } | |
| - (Target *)publiclyDependsOn: (id)dependencies | |
| { | |
| BuildAppendDependenciesFromObject(self, dependencies, true); | |
| return self; | |
| } | |
| - (void)addDependencySelector: (SEL)selector publicDependency: (bool)isPublic | |
| { | |
| for (BuildDependencySpec *dependency in _dependencies) { | |
| if (dependency->_selector == selector && | |
| dependency->_publicDependency == isPublic) | |
| return; | |
| } | |
| [_dependencies addObject: | |
| [BuildDependencySpec specWithSelector: selector | |
| publicDependency: isPublic]]; | |
| } | |
| - (Target *)outputName: (OFString *)name | |
| { | |
| _artifactName = [name copy]; | |
| return self; | |
| } | |
| - (Target *)group: (OFString *)name | |
| { | |
| _group = [name copy]; | |
| return self; | |
| } | |
| - (Target *)pmHeader: (OFString *)path | |
| { | |
| _pmHeader = [path copy]; | |
| return self; | |
| } | |
| - (Target *)workingDirectory: (OFString *)path | |
| { | |
| _workingDirectory = [path copy]; | |
| return self; | |
| } | |
| - (Target *)files: (id)paths | |
| { | |
| BuildAppendFlattenedUniqueObject(_sourcePatterns, paths); | |
| return self; | |
| } | |
| - (Target *)inputs: (id)paths | |
| { | |
| BuildAppendFlattenedUniqueObject(_extraInputs, paths); | |
| return self; | |
| } | |
| - (Target *)includeDirs: (id)paths | |
| { | |
| BuildAppendFlattenedUniqueObject(_includeDirs, paths); | |
| return self; | |
| } | |
| - (Target *)publicIncludeDirs: (id)paths | |
| { | |
| BuildAppendFlattenedUniqueObject(_publicIncludeDirs, paths); | |
| return self; | |
| } | |
| - (Target *)defines: (id)defines | |
| { | |
| BuildAppendFlattenedUniqueObject(_defines, defines); | |
| return self; | |
| } | |
| - (Target *)publicDefines: (id)defines | |
| { | |
| BuildAppendFlattenedUniqueObject(_publicDefines, defines); | |
| return self; | |
| } | |
| - (Target *)packages: (id)packages | |
| { | |
| BuildAppendFlattenedUniqueObject(_packages, packages); | |
| return self; | |
| } | |
| - (Target *)publicPackages: (id)packages | |
| { | |
| BuildAppendFlattenedUniqueObject(_publicPackages, packages); | |
| return self; | |
| } | |
| - (Target *)cFlags: (id)flags | |
| { | |
| BuildAppendFlattenedUniqueObject(_cFlags, flags); | |
| return self; | |
| } | |
| - (Target *)mFlags: (id)flags | |
| { | |
| BuildAppendFlattenedUniqueObject(_mFlags, flags); | |
| return self; | |
| } | |
| - (Target *)ldFlags: (id)flags | |
| { | |
| BuildAppendFlattenedUniqueObject(_ldFlags, flags); | |
| return self; | |
| } | |
| - (Target *)links: (id)links | |
| { | |
| BuildAppendFlattenedUniqueObject(_links, links); | |
| return self; | |
| } | |
| - (Target *)libraryDirs: (id)directories | |
| { | |
| BuildAppendFlattenedUniqueObject(_libraryDirs, directories); | |
| return self; | |
| } | |
| - (Target *)fileCFlags: (OFString *)pattern flags: (id)flags | |
| { | |
| OFMutableArray *flattenedFlags = [[OFMutableArray alloc] init]; | |
| BuildAppendFlattenedUniqueObject(flattenedFlags, flags); | |
| [_fileCFlags addObject: [BuildFileFlagRule ruleWithPattern: pattern | |
| flags: flattenedFlags]]; | |
| return self; | |
| } | |
| - (Target *)fileMFlags: (OFString *)pattern flags: (id)flags | |
| { | |
| OFMutableArray *flattenedFlags = [[OFMutableArray alloc] init]; | |
| BuildAppendFlattenedUniqueObject(flattenedFlags, flags); | |
| [_fileMFlags addObject: [BuildFileFlagRule ruleWithPattern: pattern | |
| flags: flattenedFlags]]; | |
| return self; | |
| } | |
| - (OFString *)description | |
| { | |
| return [OFString stringWithFormat: | |
| @"<Target %@ kind=%d artifact=%@>", _name, _kind, self.artifactName]; | |
| } | |
| @end | |
| static Target *BuildLoadTarget(Build *build, SEL selector) | |
| { | |
| Target *target; | |
| if (!BuildIsKnownTargetSelector(build, selector)) | |
| return nil; | |
| target = [build declaredTargetForSelector: selector]; | |
| if (target->_configured) | |
| return target; | |
| if (target->_configuring) | |
| return target; | |
| target->_configuring = true; | |
| @try { | |
| Target *(*method)(id, SEL) = | |
| (Target *(*)(id, SEL))(void *)[build methodForSelector: selector]; | |
| id returnedTarget = method(build, selector); | |
| if (returnedTarget != nil && returnedTarget != target && | |
| [returnedTarget isKindOfClass: [Target class]]) | |
| target = returnedTarget; | |
| target->_configured = true; | |
| target->_configuring = false; | |
| return target; | |
| } @catch (id exception) { | |
| target->_configuring = false; | |
| @throw exception; | |
| } | |
| } | |
| static OFString *BuildArtifactPath(Build *build, Target *target) | |
| { | |
| OFString *name = target.artifactName; | |
| switch (target->_kind) { | |
| case TargetKindBinary: | |
| return [build->_buildRoot stringByAppendingPathComponent: | |
| [name stringByAppendingString: BuildExecutableSuffix()]]; | |
| case TargetKindStaticLibrary: | |
| return [build->_buildRoot stringByAppendingPathComponent: | |
| [[[@"lib" stringByAppendingString: name] | |
| stringByAppendingPathExtension: @"a"] stringByStandardizingPath]]; | |
| case TargetKindSharedLibrary: | |
| return [build->_buildRoot stringByAppendingPathComponent: | |
| [[@"lib" stringByAppendingString: name] | |
| stringByAppendingString: BuildDynamicLibrarySuffix()]]; | |
| case TargetKindPhony: | |
| default: | |
| return nil; | |
| } | |
| } | |
| static OFString *BuildObjectPath(Build *build, Target *target, OFString *sourcePath) | |
| { | |
| OFString *objectRoot = [[[[build->_buildBaseRoot | |
| stringByAppendingPathComponent: @".objs"] | |
| stringByAppendingPathComponent: target->_name] | |
| stringByAppendingPathComponent: build->_platform] | |
| stringByAppendingPathComponent: build->_architecture]; | |
| OFString *modeRoot = [objectRoot stringByAppendingPathComponent: build->_mode]; | |
| return [[modeRoot stringByAppendingPathComponent: | |
| [sourcePath stringByAppendingString: @".o"]] stringByStandardizingPath]; | |
| } | |
| @implementation BuildBase | |
| - (instancetype)init | |
| { | |
| self = [super init]; | |
| _projectRoot = [[[OFFileManager defaultManager] currentDirectoryPath] | |
| stringByStandardizingPath]; | |
| _platform = [BuildSanitizePathComponent( | |
| [OFSystemInfo operatingSystemName] ?: @"unknown") copy]; | |
| _architecture = [BuildHostArchitecture() copy]; | |
| _buildBaseRoot = [[_projectRoot stringByAppendingPathComponent: @"build"] copy]; | |
| _targetsByIdentifier = [[OFMutableDictionary alloc] init]; | |
| _objfwPackageCPPFlagsByName = [[OFMutableDictionary alloc] init]; | |
| _objfwPackageStaticLibsByName = [[OFMutableDictionary alloc] init]; | |
| _objfwPackageLDFlagsByName = [[OFMutableDictionary alloc] init]; | |
| _mode = @"debug"; | |
| _jobs = ([OFSystemInfo numberOfCPUs] > 0 ? [OFSystemInfo numberOfCPUs] : 1); | |
| _verbose = false; | |
| BuildConfigureToolchain((Build *)self); | |
| return self; | |
| } | |
| - (OFString *)projectRoot | |
| { | |
| return _projectRoot; | |
| } | |
| - (OFString *)buildRoot | |
| { | |
| return _buildRoot; | |
| } | |
| - (OFString *)mode | |
| { | |
| return _mode; | |
| } | |
| - (void)setMode: (OFString *)mode | |
| { | |
| _mode = [mode copy]; | |
| BuildRefreshMode((Build *)self); | |
| } | |
| - (size_t)jobs | |
| { | |
| return _jobs; | |
| } | |
| - (void)setJobs: (size_t)jobs | |
| { | |
| _jobs = (jobs > 0 ? jobs : 1); | |
| } | |
| - (bool)verbose | |
| { | |
| return _verbose; | |
| } | |
| - (void)setVerbose: (bool)verbose | |
| { | |
| _verbose = verbose; | |
| } | |
| - (Target *)declaredTargetForSelector: (SEL)selector | |
| { | |
| OFString *identifier = BuildSelectorString(selector); | |
| Target *target = [_targetsByIdentifier objectForKey: identifier]; | |
| if (target == nil) { | |
| target = [[Target alloc] initWithBuild: (Build *)self | |
| selector: selector]; | |
| [_targetsByIdentifier setObject: target forKey: identifier]; | |
| } | |
| return target; | |
| } | |
| - (nullable Target *)targetNamed: (OFString *)name | |
| { | |
| SEL selector; | |
| Target *target; | |
| target = [_targetsByIdentifier objectForKey: name]; | |
| if (target != nil) | |
| return target; | |
| selector = sel_registerName(name.UTF8String); | |
| if (BuildIsKnownTargetSelector((Build *)self, selector)) | |
| return BuildLoadTarget((Build *)self, selector); | |
| for (OFString *selectorName in BuildKnownTargetSelectorNames((Build *)self)) | |
| BuildLoadTarget((Build *)self, sel_registerName(selectorName.UTF8String)); | |
| for (OFString *selectorName in _targetsByIdentifier) { | |
| Target *candidate = [_targetsByIdentifier objectForKey: selectorName]; | |
| if ([candidate.artifactName isEqual: name] || | |
| [candidate.name isEqual: name]) | |
| return candidate; | |
| } | |
| return nil; | |
| } | |
| @end | |
| @implementation BuildEngine | |
| - (instancetype)initWithBuild: (Build *)build | |
| targetNames: (OFArray *)targetNames | |
| runTargetName: (nullable OFString *)runTargetName | |
| runArguments: (nullable OFArray *)runArguments | |
| completion: (void (^)(int status))completion | |
| { | |
| self = [super init]; | |
| _build = build; | |
| _requestedTargetNames = [targetNames copy]; | |
| _runTargetName = [runTargetName copy]; | |
| _runArguments = [runArguments copy]; | |
| _completion = [completion copy]; | |
| _finalActionsByTargetName = [[OFMutableDictionary alloc] init]; | |
| _exportedIncludeDirsByTargetName = [[OFMutableDictionary alloc] init]; | |
| _exportedDefinesByTargetName = [[OFMutableDictionary alloc] init]; | |
| _exportedPackagesByTargetName = [[OFMutableDictionary alloc] init]; | |
| _exportedLinkArtifactsByTargetName = [[OFMutableDictionary alloc] init]; | |
| _allActions = [[OFMutableArray alloc] init]; | |
| _readyActions = [[OFMutableArray alloc] init]; | |
| _exitStatus = 0; | |
| return self; | |
| } | |
| - (OFArray *)exportedIncludeDirsForTarget: (Target *)target | |
| { | |
| OFArray *cached = [_exportedIncludeDirsByTargetName objectForKey: target->_name]; | |
| OFMutableArray *includeDirs; | |
| if (cached != nil) | |
| return cached; | |
| includeDirs = [[OFMutableArray alloc] init]; | |
| BuildAppendUniqueStrings(includeDirs, | |
| BuildResolvePathList(_build, target->_publicIncludeDirs)); | |
| BuildAppendUniqueStrings(includeDirs, | |
| BuildResolvePathList(_build, target->_includeDirs)); | |
| for (BuildDependencySpec *dependency in target->_dependencies) { | |
| Target *dependencyTarget; | |
| if (!dependency->_publicDependency) | |
| continue; | |
| dependencyTarget = BuildLoadTarget(_build, dependency->_selector); | |
| BuildAppendUniqueStrings(includeDirs, | |
| [self exportedIncludeDirsForTarget: dependencyTarget]); | |
| } | |
| cached = [includeDirs copy]; | |
| [_exportedIncludeDirsByTargetName setObject: cached forKey: target->_name]; | |
| return cached; | |
| } | |
| - (OFArray *)exportedDefinesForTarget: (Target *)target | |
| { | |
| OFArray *cached = [_exportedDefinesByTargetName objectForKey: target->_name]; | |
| OFMutableArray *defines; | |
| if (cached != nil) | |
| return cached; | |
| defines = [[OFMutableArray alloc] init]; | |
| BuildAppendUniqueStrings(defines, target->_publicDefines); | |
| BuildAppendUniqueStrings(defines, target->_defines); | |
| for (BuildDependencySpec *dependency in target->_dependencies) { | |
| Target *dependencyTarget; | |
| if (!dependency->_publicDependency) | |
| continue; | |
| dependencyTarget = BuildLoadTarget(_build, dependency->_selector); | |
| BuildAppendUniqueStrings(defines, | |
| [self exportedDefinesForTarget: dependencyTarget]); | |
| } | |
| cached = [defines copy]; | |
| [_exportedDefinesByTargetName setObject: cached forKey: target->_name]; | |
| return cached; | |
| } | |
| - (OFArray *)exportedPackagesForTarget: (Target *)target | |
| { | |
| OFArray *cached = [_exportedPackagesByTargetName objectForKey: target->_name]; | |
| OFMutableArray *packages; | |
| if (cached != nil) | |
| return cached; | |
| packages = [[OFMutableArray alloc] init]; | |
| BuildAppendUniqueStrings(packages, target->_publicPackages); | |
| for (BuildDependencySpec *dependency in target->_dependencies) { | |
| Target *dependencyTarget; | |
| if (!dependency->_publicDependency) | |
| continue; | |
| dependencyTarget = BuildLoadTarget(_build, dependency->_selector); | |
| BuildAppendUniqueStrings(packages, | |
| [self exportedPackagesForTarget: dependencyTarget]); | |
| } | |
| cached = [packages copy]; | |
| [_exportedPackagesByTargetName setObject: cached forKey: target->_name]; | |
| return cached; | |
| } | |
| - (OFArray *)exportedLinkArtifactsForTarget: (Target *)target | |
| { | |
| OFArray *cached = [_exportedLinkArtifactsByTargetName | |
| objectForKey: target->_name]; | |
| OFMutableArray *artifacts; | |
| OFString *artifactPath; | |
| if (cached != nil) | |
| return cached; | |
| artifacts = [[OFMutableArray alloc] init]; | |
| artifactPath = BuildArtifactPath(_build, target); | |
| if (artifactPath != nil) | |
| [artifacts addObject: artifactPath]; | |
| for (BuildDependencySpec *dependency in target->_dependencies) { | |
| Target *dependencyTarget; | |
| if (!dependency->_publicDependency) | |
| continue; | |
| dependencyTarget = BuildLoadTarget(_build, dependency->_selector); | |
| BuildAppendUniqueStrings(artifacts, | |
| [self exportedLinkArtifactsForTarget: dependencyTarget]); | |
| } | |
| cached = [artifacts copy]; | |
| [_exportedLinkArtifactsByTargetName setObject: cached forKey: target->_name]; | |
| return cached; | |
| } | |
| - (BuildAction *)compileActionForTarget: (Target *)target | |
| source: (OFString *)sourcePath | |
| { | |
| OFString *absoluteSource = BuildProjectAbsolutePath(_build, sourcePath); | |
| OFString *objectPath = BuildObjectPath(_build, target, sourcePath); | |
| OFString *depfilePath = [objectPath stringByAppendingString: @".d"]; | |
| OFString *signaturePath = [objectPath stringByAppendingString: @".signature"]; | |
| OFMutableArray *argv = [[OFMutableArray alloc] init]; | |
| OFMutableArray *inputs = [[OFMutableArray alloc] init]; | |
| OFMutableArray *defines = [[OFMutableArray alloc] init]; | |
| OFMutableArray *includeDirs = [[OFMutableArray alloc] init]; | |
| OFMutableArray *packages = [[OFMutableArray alloc] init]; | |
| OFMutableArray *flags = [[OFMutableArray alloc] init]; | |
| OFMutableArray *directDependencyInterfaces = [[OFMutableArray alloc] init]; | |
| BuildAction *action; | |
| for (BuildDependencySpec *dependency in target->_dependencies) { | |
| Target *dependencyTarget = BuildLoadTarget(_build, dependency->_selector); | |
| [directDependencyInterfaces addObject: dependencyTarget]; | |
| } | |
| [argv addObject: _build->_compiler]; | |
| [argv addObjectsFromArray: _build->_objfwCPPFlags]; | |
| [argv addObjectsFromArray: _build->_objfwObjCFlags]; | |
| [argv addObjectsFromArray: _build->_objfwArcFlags]; | |
| [argv addObjectsFromArray: _build->_modeCFlags]; | |
| [argv addObjectsFromArray: _build->_modeMFlags]; | |
| BuildAppendUniqueStrings(defines, _build->_globalDefines); | |
| BuildAppendUniqueStrings(defines, target->_defines); | |
| BuildAppendUniqueStrings(defines, target->_publicDefines); | |
| BuildAppendUniqueStrings(packages, target->_packages); | |
| BuildAppendUniqueStrings(packages, target->_publicPackages); | |
| for (Target *dependencyTarget in directDependencyInterfaces) | |
| BuildAppendUniqueStrings(defines, | |
| [self exportedDefinesForTarget: dependencyTarget]); | |
| for (Target *dependencyTarget in directDependencyInterfaces) | |
| BuildAppendUniqueStrings(packages, | |
| [self exportedPackagesForTarget: dependencyTarget]); | |
| BuildAppendUniqueStrings(includeDirs, _build->_globalIncludeDirs); | |
| BuildAppendUniqueStrings(includeDirs, target->_includeDirs); | |
| BuildAppendUniqueStrings(includeDirs, target->_publicIncludeDirs); | |
| for (Target *dependencyTarget in directDependencyInterfaces) | |
| BuildAppendUniqueStrings(includeDirs, | |
| [self exportedIncludeDirsForTarget: dependencyTarget]); | |
| [argv addObjectsFromArray: BuildResolveDefineFlags(defines)]; | |
| [argv addObjectsFromArray: BuildResolveIncludeFlags(_build, includeDirs)]; | |
| [argv addObjectsFromArray: BuildResolvePackageCPPFlags(_build, packages)]; | |
| BuildAppendUniqueStrings(flags, _build->_globalCFlags); | |
| BuildAppendUniqueStrings(flags, _build->_globalMFlags); | |
| BuildAppendUniqueStrings(flags, target->_cFlags); | |
| BuildAppendUniqueStrings(flags, target->_mFlags); | |
| BuildAppendUniqueStrings(flags, | |
| BuildCollectMatchingFlags(target->_fileCFlags, sourcePath)); | |
| BuildAppendUniqueStrings(flags, | |
| BuildCollectMatchingFlags(target->_fileMFlags, sourcePath)); | |
| [argv addObjectsFromArray: flags]; | |
| if (target->_pmHeader != nil) { | |
| [argv addObject: @"-include"]; | |
| [argv addObject: BuildProjectAbsolutePath(_build, target->_pmHeader)]; | |
| } | |
| [argv addObjectsFromArray: [OFArray arrayWithObjects: | |
| @"-MMD", @"-MF", depfilePath, @"-c", @"-o", objectPath, absoluteSource, | |
| nil]]; | |
| BuildAppendUniqueString(inputs, absoluteSource); | |
| if (target->_pmHeader != nil) | |
| BuildAppendUniqueString(inputs, | |
| BuildProjectAbsolutePath(_build, target->_pmHeader)); | |
| BuildAppendUniqueStrings(inputs, BuildResolvePathList(_build, | |
| target->_extraInputs)); | |
| action = [BuildAction actionWithKey: | |
| [@"compile:" stringByAppendingString: objectPath] | |
| label: [OFString stringWithFormat: @"compile %@", | |
| sourcePath]]; | |
| action->_argv = [argv copy]; | |
| action->_inputs = [inputs copy]; | |
| action->_outputs = [OFArray arrayWithObjects: | |
| objectPath, depfilePath, signaturePath, nil]; | |
| action->_signaturePath = signaturePath; | |
| action->_signature = BuildActionSignature(action); | |
| action->_kind = ActionKindCommand; | |
| action->_alwaysBuild = target->_alwaysBuild; | |
| action->_environment = _build->_subprocessEnvironment; | |
| action->_dynamicInputsProvider = [^ OFArray * { | |
| return BuildParseDepfile(depfilePath); | |
| } copy]; | |
| [_allActions addObject: action]; | |
| return action; | |
| } | |
| - (BuildAction *)finalActionForTarget: (Target *)target | |
| { | |
| BuildAction *cached = [_finalActionsByTargetName objectForKey: target->_name]; | |
| OFMutableArray *dependencyActions; | |
| OFMutableArray *compileActions; | |
| OFArray *sources; | |
| OFString *artifactPath; | |
| OFString *signaturePath; | |
| BuildAction *finalAction; | |
| if (cached != nil) | |
| return cached; | |
| target = BuildLoadTarget(_build, target->_selector); | |
| dependencyActions = [[OFMutableArray alloc] init]; | |
| for (BuildDependencySpec *dependency in target->_dependencies) { | |
| Target *dependencyTarget = BuildLoadTarget(_build, dependency->_selector); | |
| [dependencyActions addObject: [self finalActionForTarget: dependencyTarget]]; | |
| } | |
| sources = BuildResolvedSourcePaths(_build, target); | |
| compileActions = [[OFMutableArray alloc] init]; | |
| for (OFString *sourcePath in sources) | |
| [compileActions addObject: [self compileActionForTarget: target | |
| source: sourcePath]]; | |
| artifactPath = BuildArtifactPath(_build, target); | |
| signaturePath = (artifactPath != nil | |
| ? [artifactPath stringByAppendingString: @".signature"] | |
| : nil); | |
| finalAction = [BuildAction actionWithKey: | |
| [@"target:" stringByAppendingString: target->_name] | |
| label: [OFString stringWithFormat: | |
| @"target %@", target->_name]]; | |
| [_finalActionsByTargetName setObject: finalAction forKey: target->_name]; | |
| [_allActions addObject: finalAction]; | |
| for (BuildAction *dependencyAction in dependencyActions) | |
| BuildAddActionDependency(finalAction, dependencyAction); | |
| for (BuildAction *compileAction in compileActions) | |
| BuildAddActionDependency(finalAction, compileAction); | |
| finalAction->_alwaysBuild = target->_alwaysBuild; | |
| if (target->_kind == TargetKindPhony) { | |
| finalAction->_kind = ActionKindNoOp; | |
| finalAction->_signature = @"phony"; | |
| finalAction->_label = [OFString stringWithFormat: @"phony %@", | |
| target->_name]; | |
| return finalAction; | |
| } | |
| if (target->_kind == TargetKindStaticLibrary) { | |
| OFMutableArray *argv = [[OFMutableArray alloc] init]; | |
| OFMutableArray *inputs = [[OFMutableArray alloc] init]; | |
| [argv addObjectsFromArray: [OFArray arrayWithObjects: | |
| _build->_archiver, @"rcs", artifactPath, nil]]; | |
| for (BuildAction *compileAction in compileActions) { | |
| [argv addObject: [compileAction->_outputs objectAtIndex: 0]]; | |
| [inputs addObject: [compileAction->_outputs objectAtIndex: 0]]; | |
| } | |
| finalAction->_argv = [argv copy]; | |
| finalAction->_inputs = [inputs copy]; | |
| finalAction->_outputs = [OFArray arrayWithObjects: | |
| artifactPath, signaturePath, nil]; | |
| finalAction->_signaturePath = signaturePath; | |
| finalAction->_kind = ActionKindCommand; | |
| finalAction->_label = [OFString stringWithFormat: @"archive %@", | |
| BuildDisplayPath(_build, artifactPath)]; | |
| finalAction->_signature = BuildActionSignature(finalAction); | |
| finalAction->_environment = _build->_subprocessEnvironment; | |
| return finalAction; | |
| } | |
| { | |
| OFMutableArray *argv = [[OFMutableArray alloc] init]; | |
| OFMutableArray *inputs = [[OFMutableArray alloc] init]; | |
| OFMutableArray *libraryDirs = [[OFMutableArray alloc] init]; | |
| OFMutableArray *linkFlags = [[OFMutableArray alloc] init]; | |
| OFMutableArray *linkArtifacts = [[OFMutableArray alloc] init]; | |
| OFMutableArray *packages = [[OFMutableArray alloc] init]; | |
| OFMutableArray *objfwStaticLibs = [[OFMutableArray alloc] init]; | |
| [argv addObject: _build->_compiler]; | |
| [argv addObject: @"-o"]; | |
| [argv addObject: artifactPath]; | |
| for (BuildAction *compileAction in compileActions) { | |
| OFString *objectPath = [compileAction->_outputs objectAtIndex: 0]; | |
| [argv addObject: objectPath]; | |
| [inputs addObject: objectPath]; | |
| } | |
| for (BuildDependencySpec *dependency in target->_dependencies) { | |
| Target *dependencyTarget = BuildLoadTarget(_build, dependency->_selector); | |
| BuildAppendUniqueStrings(linkArtifacts, | |
| [self exportedLinkArtifactsForTarget: dependencyTarget]); | |
| BuildAppendUniqueStrings(packages, | |
| [self exportedPackagesForTarget: dependencyTarget]); | |
| } | |
| for (OFString *linkArtifact in linkArtifacts) { | |
| [argv addObject: linkArtifact]; | |
| [inputs addObject: linkArtifact]; | |
| } | |
| BuildAppendUniqueStrings(packages, target->_packages); | |
| BuildAppendUniqueStrings(packages, target->_publicPackages); | |
| [libraryDirs addObjectsFromArray: | |
| BuildResolveLibraryDirectoryFlags(_build, target->_libraryDirs)]; | |
| [linkFlags addObjectsFromArray: _build->_globalLDFlags]; | |
| [linkFlags addObjectsFromArray: _build->_modeLDFlags]; | |
| [linkFlags addObjectsFromArray: target->_ldFlags]; | |
| BuildAppendUniqueStrings(linkFlags, | |
| BuildResolvePackageLDFlags(_build, packages)); | |
| BuildAppendUniqueStrings(linkFlags, _build->_objfwLDFlags); | |
| if (packages.count > 0) | |
| BuildAppendUniqueStrings(objfwStaticLibs, | |
| BuildResolvePackageStaticLibs(_build, packages)); | |
| else | |
| BuildAppendUniqueStrings(objfwStaticLibs, _build->_objfwStaticLibs); | |
| [argv addObjectsFromArray: libraryDirs]; | |
| [argv addObjectsFromArray: BuildResolveLinkFlags(target->_links)]; | |
| [argv addObjectsFromArray: objfwStaticLibs]; | |
| [argv addObjectsFromArray: linkFlags]; | |
| if (target->_kind == TargetKindSharedLibrary) | |
| [argv addObject: @"-shared"]; | |
| finalAction->_argv = [argv copy]; | |
| finalAction->_inputs = [inputs copy]; | |
| finalAction->_outputs = [OFArray arrayWithObjects: | |
| artifactPath, signaturePath, nil]; | |
| finalAction->_signaturePath = signaturePath; | |
| finalAction->_kind = ActionKindCommand; | |
| finalAction->_label = [OFString stringWithFormat: @"link %@", | |
| BuildDisplayPath(_build, artifactPath)]; | |
| finalAction->_signature = BuildActionSignature(finalAction); | |
| finalAction->_environment = _build->_subprocessEnvironment; | |
| } | |
| return finalAction; | |
| } | |
| - (void)enqueueReadyActions | |
| { | |
| for (BuildAction *action in _allActions) { | |
| if (action->_completed || action->_remainingDependencies > 0) | |
| continue; | |
| if (action->_process != nil) | |
| continue; | |
| if ([_readyActions containsObject: action]) | |
| continue; | |
| [_readyActions addObject: action]; | |
| } | |
| } | |
| - (void)completeAction: (BuildAction *)action | |
| success: (bool)success | |
| skipped: (bool)skipped | |
| { | |
| if (action->_completed) | |
| return; | |
| action->_completed = true; | |
| action->_succeeded = success; | |
| action->_skipped = skipped; | |
| if (action->_process != nil) { | |
| action->_process = nil; | |
| if (_activeActions > 0) | |
| _activeActions--; | |
| } | |
| if (!success) { | |
| _failed = true; | |
| _exitStatus = 1; | |
| } | |
| for (BuildAction *dependent in action->_dependents) { | |
| if (dependent->_remainingDependencies > 0) | |
| dependent->_remainingDependencies--; | |
| } | |
| [self enqueueReadyActions]; | |
| [self drainReadyQueue]; | |
| } | |
| - (void)finishIfPossible | |
| { | |
| if (_activeActions > 0 || _readyActions.count > 0) | |
| return; | |
| if (!_failed && _runTargetName != nil && !_launchedRunTarget) { | |
| Target *runTarget = [_build targetNamed: _runTargetName]; | |
| OFString *artifactPath; | |
| _launchedRunTarget = true; | |
| if (runTarget == nil || runTarget->_kind != TargetKindBinary) { | |
| [OFStdErr writeFormat: | |
| @"Cannot run %@ because it is not a binary target.\n", | |
| _runTargetName]; | |
| _completion(1); | |
| return; | |
| } | |
| artifactPath = BuildArtifactPath(_build, runTarget); | |
| @try { | |
| _runProcess = [OFSubprocess subprocessWithProgram: artifactPath | |
| arguments: _runArguments]; | |
| } @catch (id exception) { | |
| [OFStdErr writeFormat: @"Run failed to launch %@: %@\n", | |
| artifactPath, exception]; | |
| _completion(1); | |
| return; | |
| } | |
| [OFStdOut writeFormat: @"Running %@\n", | |
| BuildDisplayPath(_build, artifactPath)]; | |
| [_runProcess closeForWriting]; | |
| [self performSelector: @selector(pollRunProcess) afterDelay: 0]; | |
| return; | |
| } | |
| _completion(_exitStatus); | |
| } | |
| - (void)launchAction: (BuildAction *)action | |
| { | |
| OFFileManager *fileManager = [OFFileManager defaultManager]; | |
| if (action->_kind == ActionKindNoOp) { | |
| if (_build->_verbose) | |
| [OFStdOut writeFormat: @"%@: no-op\n", action->_label]; | |
| [self completeAction: action success: true skipped: true]; | |
| return; | |
| } | |
| if (BuildActionIsUpToDate(action)) { | |
| if (_build->_verbose) | |
| [OFStdOut writeFormat: @"%@: up to date\n", action->_label]; | |
| [self completeAction: action success: true skipped: true]; | |
| return; | |
| } | |
| for (OFString *output in action->_outputs) { | |
| OFString *directory = output.stringByDeletingLastPathComponent; | |
| if (directory.length > 0) | |
| [fileManager createDirectoryAtPath: directory createParents: true]; | |
| } | |
| if (_build->_verbose) | |
| [OFStdOut writeFormat: @"%@: %@\n", action->_label, | |
| [action->_argv componentsJoinedByString: @" "]]; | |
| @try { | |
| action->_process = [OFSubprocess subprocessWithProgram: | |
| [action->_argv objectAtIndex: 0] | |
| programName: | |
| [action->_argv objectAtIndex: 0] | |
| arguments: | |
| [action->_argv objectsInRange: OFMakeRange(1, | |
| action->_argv.count - 1)] | |
| environment: | |
| action->_environment]; | |
| } @catch (id exception) { | |
| [OFStdErr writeFormat: @"%@: failed to launch: %@\n", | |
| action->_label, exception]; | |
| [self completeAction: action success: false skipped: false]; | |
| [self finishIfPossible]; | |
| return; | |
| } | |
| _activeActions++; | |
| [action->_process closeForWriting]; | |
| [self performSelector: @selector(pollActionOutput:) | |
| withObject: action | |
| afterDelay: 0]; | |
| } | |
| - (void)pollActionOutput: (BuildAction *)action | |
| { | |
| while (true) { | |
| OFString *line; | |
| @try { | |
| line = [action->_process tryReadLineWithEncoding: | |
| OFStringEncodingUTF8]; | |
| } @catch (id exception) { | |
| [OFStdErr writeFormat: @"%@: %@\n", action->_label, exception]; | |
| [self completeAction: action success: false skipped: false]; | |
| [self finishIfPossible]; | |
| return; | |
| } | |
| if (line == nil) | |
| break; | |
| [OFStdOut writeFormat: @"%@: %@\n", action->_label, line]; | |
| } | |
| if (action->_process.atEndOfStream) { | |
| if ([action->_process waitForTermination] != 0) { | |
| [OFStdErr writeFormat: @"%@: command failed\n", action->_label]; | |
| [self completeAction: action success: false skipped: false]; | |
| [self finishIfPossible]; | |
| return; | |
| } | |
| [action->_signature writeToFile: action->_signaturePath]; | |
| [self completeAction: action success: true skipped: false]; | |
| [self finishIfPossible]; | |
| return; | |
| } | |
| [self performSelector: @selector(pollActionOutput:) | |
| withObject: action | |
| afterDelay: 0.02]; | |
| } | |
| - (void)pollRunProcess | |
| { | |
| while (true) { | |
| OFString *line; | |
| @try { | |
| line = [_runProcess tryReadLineWithEncoding: OFStringEncodingUTF8]; | |
| } @catch (id exception) { | |
| [OFStdErr writeFormat: | |
| @"Run failed while reading output: %@\n", exception]; | |
| _completion(1); | |
| return; | |
| } | |
| if (line == nil) | |
| break; | |
| [OFStdOut writeLine: line]; | |
| } | |
| if (_runProcess.atEndOfStream) { | |
| _completion([_runProcess waitForTermination]); | |
| return; | |
| } | |
| [self performSelector: @selector(pollRunProcess) afterDelay: 0.02]; | |
| } | |
| - (void)drainReadyQueue | |
| { | |
| while (!_failed && _activeActions < _build->_jobs && _readyActions.count > 0) { | |
| BuildAction *action = [_readyActions objectAtIndex: 0]; | |
| [_readyActions removeObjectAtIndex: 0]; | |
| [self launchAction: action]; | |
| } | |
| [self finishIfPossible]; | |
| } | |
| - (void)start | |
| { | |
| OFMutableArray *targets = [[OFMutableArray alloc] init]; | |
| if (_requestedTargetNames.count == 0) { | |
| Target *defaultTarget = [_build targetNamed: @"mainTarget"]; | |
| if (defaultTarget == nil) { | |
| [OFStdErr writeLine: | |
| @"No target specified and no mainTarget target is defined."]; | |
| _completion(1); | |
| return; | |
| } | |
| [targets addObject: defaultTarget]; | |
| } else { | |
| for (OFString *targetName in _requestedTargetNames) { | |
| Target *target = [_build targetNamed: targetName]; | |
| if (target == nil) { | |
| [OFStdErr writeFormat: @"Unknown target: %@\n", targetName]; | |
| _completion(1); | |
| return; | |
| } | |
| [targets addObject: target]; | |
| } | |
| } | |
| if (_runTargetName != nil) { | |
| Target *runTarget = [_build targetNamed: _runTargetName]; | |
| if (runTarget == nil) { | |
| [OFStdErr writeFormat: @"Unknown run target: %@\n", _runTargetName]; | |
| _completion(1); | |
| return; | |
| } | |
| if (![targets containsObject: runTarget]) | |
| [targets addObject: runTarget]; | |
| } | |
| for (Target *target in targets) | |
| [self finalActionForTarget: target]; | |
| for (BuildAction *action in _allActions) | |
| action->_remainingDependencies = action->_dependencies.count; | |
| [self enqueueReadyActions]; | |
| [self drainReadyQueue]; | |
| } | |
| @end | |
| static void BuildListTargets(Build *build) | |
| { | |
| OFMutableArray *targets = [[OFMutableArray alloc] init]; | |
| for (OFString *selectorName in BuildKnownTargetSelectorNames(build)) | |
| [targets addObject: BuildLoadTarget(build, sel_registerName( | |
| selectorName.UTF8String))]; | |
| [OFStdOut writeLine: @"Available targets:"]; | |
| for (Target *target in targets) { | |
| OFString *kind; | |
| switch (target->_kind) { | |
| case TargetKindStaticLibrary: | |
| kind = @"static"; | |
| break; | |
| case TargetKindSharedLibrary: | |
| kind = @"shared"; | |
| break; | |
| case TargetKindPhony: | |
| kind = @"phony"; | |
| break; | |
| case TargetKindBinary: | |
| default: | |
| kind = @"binary"; | |
| break; | |
| } | |
| [OFStdOut writeFormat: @" %@ (%@)", | |
| target.name, kind]; | |
| if (target->_group != nil) | |
| [OFStdOut writeFormat: @" group=%@", target->_group]; | |
| if (target->_defaultTarget != 0) | |
| [OFStdOut writeFormat: @" default=yes"]; | |
| if (target.artifactName != nil && target->_kind != TargetKindPhony) | |
| [OFStdOut writeFormat: @" artifact=%@", | |
| BuildDisplayPath(build, BuildArtifactPath(build, target))]; | |
| [OFStdOut writeLine: @""]; | |
| } | |
| } | |
| static void BuildClean(Build *build) | |
| { | |
| OFFileManager *fileManager = [OFFileManager defaultManager]; | |
| if ([fileManager fileExistsAtPath: build->_buildBaseRoot]) | |
| [fileManager removeItemAtPath: build->_buildBaseRoot]; | |
| [OFStdOut writeFormat: @"Removed %@\n", | |
| BuildDisplayPath(build, build->_buildBaseRoot)]; | |
| } | |
| static void BuildPrintUsage(void) | |
| { | |
| [OFStdOut writeLine: | |
| @"Usage: build [list|clean|run] [targets...] [--mode MODE] [-j JOBS] [-v] [-- args...]"]; | |
| [OFStdOut writeLine: | |
| @" build Build mainTarget by default"]; | |
| [OFStdOut writeLine: | |
| @" build list List target methods exported by Build"]; | |
| [OFStdOut writeLine: | |
| @" build clean Remove the build directory"]; | |
| [OFStdOut writeLine: | |
| @" build run [target] Build and run a binary target"]; | |
| } | |
| @interface App () | |
| { | |
| BuildEngine *_engine; | |
| } | |
| @end | |
| @implementation App | |
| - (void)applicationDidFinishLaunching: (OFNotification *)notification | |
| { | |
| Build *build; | |
| OFArray *arguments = [OFApplication arguments] ?: [OFArray array]; | |
| OFMutableArray *targetNames = [[OFMutableArray alloc] init]; | |
| OFMutableArray *runArguments = [[OFMutableArray alloc] init]; | |
| OFString *command = @"build"; | |
| OFString *runTargetName = nil; | |
| bool parsingRunArguments = false; | |
| @try { | |
| build = [[Build alloc] init]; | |
| } @catch (id exception) { | |
| [OFStdErr writeFormat: @"Failed to initialize build: %@\n", exception]; | |
| [OFApplication terminateWithStatus: 1]; | |
| return; | |
| } | |
| for (size_t index = 0; index < arguments.count; index++) { | |
| OFString *argument = [arguments objectAtIndex: index]; | |
| if (parsingRunArguments) { | |
| [runArguments addObject: argument]; | |
| continue; | |
| } | |
| if ([argument isEqual: @"--"]) { | |
| parsingRunArguments = true; | |
| continue; | |
| } | |
| if ([argument isEqual: @"--help"] || [argument isEqual: @"help"]) { | |
| BuildPrintUsage(); | |
| [OFApplication terminate]; | |
| return; | |
| } | |
| if ([argument isEqual: @"-v"] || [argument isEqual: @"--verbose"]) { | |
| build.verbose = true; | |
| continue; | |
| } | |
| if ([argument isEqual: @"-j"] || [argument isEqual: @"--jobs"]) { | |
| if (index + 1 >= arguments.count) { | |
| [OFStdErr writeLine: @"Missing value for jobs option."]; | |
| [OFApplication terminateWithStatus: 1]; | |
| return; | |
| } | |
| build.jobs = [[arguments objectAtIndex: ++index] | |
| unsignedLongLongValue]; | |
| continue; | |
| } | |
| if ([argument hasPrefix: @"--jobs="]) { | |
| build.jobs = [[argument substringFromIndex: 7] | |
| unsignedLongLongValue]; | |
| continue; | |
| } | |
| if ([argument isEqual: @"--mode"]) { | |
| if (index + 1 >= arguments.count) { | |
| [OFStdErr writeLine: @"Missing value for mode option."]; | |
| [OFApplication terminateWithStatus: 1]; | |
| return; | |
| } | |
| build.mode = [arguments objectAtIndex: ++index]; | |
| continue; | |
| } | |
| if ([argument hasPrefix: @"--mode="]) { | |
| build.mode = [argument substringFromIndex: 7]; | |
| continue; | |
| } | |
| if ([argument isEqual: @"list"] || [argument isEqual: @"clean"] || | |
| [argument isEqual: @"run"]) { | |
| command = argument; | |
| continue; | |
| } | |
| if ([command isEqual: @"run"] && runTargetName == nil) { | |
| runTargetName = argument; | |
| continue; | |
| } | |
| [targetNames addObject: argument]; | |
| } | |
| if ([command isEqual: @"list"]) { | |
| BuildListTargets(build); | |
| [OFApplication terminate]; | |
| return; | |
| } | |
| if ([command isEqual: @"clean"]) { | |
| BuildClean(build); | |
| [OFApplication terminate]; | |
| return; | |
| } | |
| if ([command isEqual: @"run"] && runTargetName == nil) | |
| runTargetName = @"app"; | |
| if ([command isEqual: @"run"] && targetNames.count == 0 && runTargetName != nil) | |
| [targetNames addObject: runTargetName]; | |
| _engine = [[BuildEngine alloc] initWithBuild: build | |
| targetNames: targetNames | |
| runTargetName: runTargetName | |
| runArguments: runArguments | |
| completion: ^ (int status) { | |
| _engine = nil; | |
| [OFApplication terminateWithStatus: status]; | |
| }]; | |
| [_engine start]; | |
| } | |
| @end | |
| #ifdef __clang__ | |
| # pragma clang diagnostic push | |
| # pragma clang diagnostic ignored "-Wnullability-completeness-on-arrays" | |
| # pragma clang diagnostic ignored "-Wnullability-inferred-on-nested-type" | |
| #endif | |
| OF_APPLICATION_DELEGATE(App); | |
| #ifdef __clang__ | |
| # pragma clang diagnostic pop | |
| #endif | |
| #pragma clang assume_nonnull end | |
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include "build.h" | |
| @implementation Build | |
| - (Target *)mainTarget | |
| { | |
| Target *target = BUILD_TARGET.phony.defaultTarget; | |
| return [target dependsOn: @[ self.app, self.asyncRuntimeTests ]]; | |
| } | |
| - (Target *)utilities | |
| { | |
| Target *target = BUILD_TARGET.staticLibrary; | |
| [target outputName: @"Utilities"]; | |
| [target pmHeader: @"src/Utilities/common.h"]; | |
| return [target files: @[ @"src/Utilities/**.m" ]]; | |
| } | |
| - (Target *)async | |
| { | |
| Target *target = BUILD_TARGET.staticLibrary; | |
| [target outputName: @"Async"]; | |
| [target publicPackages: @[ @"ObjFWTLS" ]]; | |
| [target publiclyDependsOn: @[ self.utilities ]]; | |
| [target files: @[ | |
| @"src/Async/Coroutine.m", | |
| @"src/Async/**.m|src/Async/Coroutine.m" | |
| ]]; | |
| return [target fileMFlags: @"src/Async/Coroutine.m" | |
| flags: @[ @"-fno-objc-arc" ]]; | |
| } | |
| - (Target *)app | |
| { | |
| Target *target = BUILD_TARGET.binary; | |
| [target outputName: @"App"]; | |
| [target dependsOn: @[ self.async ]]; | |
| [target pmHeader: @"src/Utilities/common.h"]; | |
| return [target files: @[ @"src/App/**.m" ]]; | |
| } | |
| - (Target *)asyncRuntimeTests | |
| { | |
| Target *target = BUILD_TARGET.binary; | |
| [target outputName: @"async-runtime-tests"]; | |
| [target group: @"tests"]; | |
| [target dependsOn: @[ self.async ]]; | |
| [target pmHeader: @"src/Utilities/common.h"]; | |
| [target mFlags: @[ | |
| @"-Wno-nonnull", | |
| @"-Wno-nullability-completeness", | |
| @"-Wno-nullable-to-nonnull-conversion" | |
| ]]; | |
| [target links: @[ @"objfwtest", @"objfwhid" ]]; | |
| return [target files: @[ @"tests/*.m" ]]; | |
| } | |
| @end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment