1// Copyright 2015-present 650 Industries. All rights reserved.
2
3#import "EXBuildConstants.h"
4#import "EXVersions.h"
5#import "EXKernelUtil.h"
6
7@interface EXVersions ()
8
9- (void)_loadVersions;
10
11@end
12
13@implementation NSString (EXVersions)
14
15- (NSArray <NSNumber *>*)versionComponents
16{
17  NSArray *stringComponents = [self componentsSeparatedByString:@"."];
18  NSMutableArray <NSNumber *>* components = [NSMutableArray arrayWithCapacity:stringComponents.count];
19  for (NSString *component in stringComponents) {
20    [components addObject:@([component integerValue])];
21  }
22  return components;
23}
24
25@end
26
27@implementation EXVersions
28
29+ (nonnull instancetype)sharedInstance
30{
31  static EXVersions *theVersions;
32  static dispatch_once_t once;
33  dispatch_once(&once, ^{
34    if (!theVersions) {
35      theVersions = [[EXVersions alloc] init];
36    }
37  });
38  return theVersions;
39}
40
41- (instancetype)init
42{
43  if (self = [super init]) {
44    [self _loadVersions];
45  }
46  return self;
47}
48
49+ (NSString *)versionedString:(NSString *)string withPrefix:(NSString *)symbolPrefix
50{
51  if (!string || !symbolPrefix) {
52    return nil;
53  }
54  return [NSString stringWithFormat:@"%@%@", symbolPrefix, string];
55}
56
57- (NSString *)symbolPrefixForSdkVersion:(NSString *)version isKernel:(BOOL)isKernel
58{
59  NSDictionary *detachedVersions = _versions[@"detachedNativeVersions"];
60  if (detachedVersions) {
61    if (!isKernel && detachedVersions[@"shell"]) {
62      // we are in a detached shell scenario, so we always want to leave the shell unprefixed
63      return @"";
64    }
65    if (isKernel && detachedVersions[@"shell"] && detachedVersions[@"kernel"]) {
66      if ([detachedVersions[@"shell"] isEqualToString:detachedVersions[@"kernel"]]) {
67        // kernel version matches shell version, so run them both unprefixed
68        return @"";
69      } else {
70        // kernel needs to run on prefixed code for the given kernel version, continue
71        version = detachedVersions[@"kernel"];
72      }
73    }
74  }
75  if (version && version.length && ![version isEqualToString:@"UNVERSIONED"]) {
76    return [[@"ABI" stringByAppendingString:version] stringByReplacingOccurrencesOfString:@"." withString:@"_"];
77  }
78  return @"";
79}
80
81- (NSString *)availableSdkVersionForManifest: (NSDictionary * _Nullable)manifest
82{
83  return [self _versionForManifest:manifest];
84}
85
86#pragma mark - Internal
87
88- (NSString *)_versionForManifest:(NSDictionary * _Nullable)manifest
89{
90  if (manifest && manifest[@"sdkVersion"]) {
91    NSString *sdkVersion = manifest[@"sdkVersion"];
92    NSArray *sdkVersions = _versions[@"sdkVersions"];
93    if (sdkVersion && sdkVersions) {
94      for (NSString *availableVersion in sdkVersions) {
95        if ([sdkVersion isEqualToString:availableVersion]) {
96          if (_temporarySdkVersion) {
97            NSArray <NSNumber *>* versionComponents = [availableVersion versionComponents];
98            BOOL isTemporary = (versionComponents.count > 1 && versionComponents[1].integerValue != 0);
99            if (isTemporary && [availableVersion isEqualToString:_temporarySdkVersion]) {
100              // no prefix if we're just using the current version
101              break;
102            }
103          }
104          return availableVersion;
105        }
106      }
107    }
108  }
109  return @"";
110}
111
112- (void)_loadVersions
113{
114  NSString *versionsPath = [[NSBundle mainBundle] pathForResource:@"EXSDKVersions" ofType:@"plist"];
115  NSMutableDictionary *mutableVersions = (versionsPath) ? [NSMutableDictionary dictionaryWithContentsOfFile:versionsPath] : [NSMutableDictionary dictionary];
116  if (mutableVersions[@"detachedNativeVersions"]) {
117    NSDictionary *detachedNativeVersions = mutableVersions[@"detachedNativeVersions"];
118    _temporarySdkVersion = detachedNativeVersions[@"shell"];
119  } else {
120    _temporarySdkVersion = [EXBuildConstants sharedInstance].temporarySdkVersion;
121  }
122  if (!_temporarySdkVersion) {
123    // no temporary sdk version specified in any way, fall back to using the highest version
124    NSArray *sdkVersions = mutableVersions[@"sdkVersions"];
125    NSUInteger highestVersion = 0;
126    if (sdkVersions) {
127      for (NSString *availableVersion in sdkVersions) {
128        NSArray <NSNumber *>* versionComponents = [availableVersion versionComponents];
129        if (versionComponents.count > 1 && versionComponents[0].integerValue > highestVersion) {
130          highestVersion = versionComponents[0].integerValue;
131          _temporarySdkVersion = availableVersion;
132        }
133      }
134    }
135  }
136
137  NSAssert((mutableVersions[@"sdkVersions"] != nil), @"No SDK versions are specified for the Expo kernel. Is the project missing EXSDKVersions.plist?");
138
139  _versions = mutableVersions;
140}
141
142- (BOOL)supportsVersion:(NSString *)sdkVersion {
143  return [_versions[@"sdkVersions"] containsObject:(NSString *) sdkVersion];
144}
145
146@end
147